#pragma semicolon 1 #pragma newdecls required //#define DEBUG #define PLUGIN_VERSION "1.0" #define DEBUG_SCENE_PARSE 1 #define DEBUG_BLOCKERS 1 #include #include //#include #include // #include #include #include #include #undef REQUIRE_PLUGIN #include int g_iLaserIndex; #if defined DEBUG_BLOCKERS #include #endif #define ENT_PROP_NAME "randomizer" #define ENT_ENV_NAME "randomizer" #define ENT_BLOCKER_NAME "randomizer" #include #define MAX_SCENE_NAME_LENGTH 32 #define MAX_INPUTS_CLASSNAME_LENGTH 64 ConVar cvarEnabled; enum struct ActiveSceneData { char name[MAX_SCENE_NAME_LENGTH]; int variantIndex; } MapData g_MapData; BuilderData g_builder; char currentMap[64]; enum struct BuilderData { JSONObject mapData; JSONObject selectedSceneData; char selectedSceneId[64]; JSONObject selectedVariantData; int selectedVariantIndex; void Cleanup() { this.selectedSceneData = null; this.selectedVariantData = null; this.selectedVariantIndex = -1; this.selectedSceneId[0] = '\0'; if(this.mapData != null) delete this.mapData; // JSONcleanup_and_delete(this.mapData); } bool SelectScene(const char[] group) { if(!g_builder.mapData.HasKey(group)) return false; this.selectedSceneData = view_as(g_builder.mapData.Get(group)); strcopy(this.selectedSceneId, sizeof(this.selectedSceneId), group); return true; } /** * Select a variant, enter -1 to not select any (scene's entities) */ bool SelectVariant(int index = -1) { if(this.selectedSceneData == null) LogError("SelectVariant called, but no group selected"); JSONArray variants = view_as(this.selectedSceneData.Get("variants")); if(index >= variants.Length) return false; else if(index < -1) return false; else if(index > -1) { this.selectedVariantData = view_as(variants.Get(index)); } else { this.selectedVariantData = null; } this.selectedVariantIndex = index; return true; } void AddEntity(int entity, ExportType exportType = Export_Model) { JSONArray entities; if(g_builder.selectedVariantData == null) { // Create .entities if doesn't exist: if(!g_builder.selectedSceneData.HasKey("entities")) { g_builder.selectedSceneData.Set("entities", new JSONArray()); } entities = view_as(g_builder.selectedSceneData.Get("entities")); } else { entities = view_as(g_builder.selectedVariantData.Get("entities")); } JSONObject entityData = ExportEntity(entity, Export_Model); entities.Push(entityData); } } #include #include public Plugin myinfo = { name = "L4D2 Randomizer", author = "jackzmc", description = "", version = PLUGIN_VERSION, url = "https://github.com/Jackzmc/sourcemod-plugins" }; public void OnPluginStart() { EngineVersion g_Game = GetEngineVersion(); if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) { SetFailState("This plugin is for L4D/L4D2 only."); } HookEvent("round_start_post_nav", Event_RoundStartPosNav); 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"); HookEvent("player_first_spawn", Event_PlayerFirstSpawn); g_MapData.activeScenes = new ArrayList(sizeof(ActiveSceneData)); } void Event_PlayerFirstSpawn(Event event, const char[] name ,bool dontBroadcast) { int client = GetClientOfUserId(event.GetInt("userid")); if(GetUserFlagBits(client) & ADMFLAG_CHAT) { // If enabled but no map loaded: if(cvarEnabled.BoolValue && g_MapData.scenes == null) PrintToChat(client, "Randomizer Note: This map has no randomizer support at the moment."); } } void Event_RoundStartPosNav(Event event, const char[] name ,bool dontBroadcast) { if(cvarEnabled.BoolValue) CreateTimer(5.0, Timer_Run); } // TODO: on round start public void OnMapStart() { g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt", true); GetCurrentMap(currentMap, sizeof(currentMap)); } public void OnMapEnd() { g_builder.Cleanup(); Cleanup(); } public void OnMapInit(const char[] map) { // if(cvarEnabled.BoolValue) { // if(LoadMapData(currentMap, FLAG_NONE) && g_MapData.lumpEdits.Length > 0) { // Log("Found %d lump edits, running...", g_MapData.lumpEdits.Length); // LumpEditData lump; // for(int i = 0; i < g_MapData.lumpEdits.Length; i++) { // g_MapData.lumpEdits.GetArray(i, lump); // lump.Trigger(); // } // hasRan = true; // } // } } public void OnConfigsExecuted() { } Action Timer_Run(Handle h) { if(cvarEnabled.BoolValue) RunMap(currentMap, FLAG_NONE); return Plugin_Handled; } stock int GetLookingEntity(int client, TraceEntityFilter filter) { float pos[3], ang[3]; GetClientEyePosition(client, pos); GetClientEyeAngles(client, ang); TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); if(TR_DidHit()) { return TR_GetEntityIndex(); } return -1; } stock int GetLookingPosition(int client, TraceEntityFilter filter, float pos[3]) { float ang[3]; GetClientEyePosition(client, pos); GetClientEyeAngles(client, ang); TR_TraceRayFilter(pos, ang, MASK_SOLID, RayType_Infinite, filter, client); if(TR_DidHit()) { TR_GetEndPosition(pos); return TR_GetEntityIndex(); } return -1; } public Action Command_CycleRandom(int client, int args) { if(args > 0) { DeleteCustomEnts(); int flags = GetCmdArgInt(1); if(flags != -1) { RunMap(currentMap, flags | view_as(FLAG_REFRESH)); } if(client > 0) PrintCenterText(client, "Cycled flags=%d", flags); } else { ReplyToCommand(client, "Active Scenes (%d/%d):", g_MapData.activeScenes.Length, g_MapData.scenes.Length); ActiveSceneData scene; for(int i = 0; i < g_MapData.activeScenes.Length; i++) { g_MapData.activeScenes.GetArray(i, scene); ReplyToCommand(client, "\t%s: variant #%d", scene.name, scene.variantIndex); } } return Plugin_Handled; } Action Command_ExportEnt(int client, int args) { float origin[3]; int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin); float angles[3]; float size[3]; char arg1[32]; GetCmdArg(1, arg1, sizeof(arg1)); if(entity > 0) { GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin); GetEntPropVector(entity, Prop_Send, "m_angRotation", angles); GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size); char model[128]; ReplyToCommand(client, "{"); GetEntityClassname(entity, model, sizeof(model)); if(StrContains(model, "prop_") == -1) { ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]); } if(StrEqual(arg1, "hammerid")) { int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID"); ReplyToCommand(client, "\t\"type\": \"hammerid\","); ReplyToCommand(client, "\t\"model\": \"%d\",", hammerid); } else if(StrEqual(arg1, "targetname")) { GetEntPropString(entity, Prop_Data, "m_iName", model, sizeof(model)); ReplyToCommand(client, "\t\"type\": \"targetname\","); ReplyToCommand(client, "\t\"model\": \"%s\",", model); } else { GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model)); ReplyToCommand(client, "\t\"model\": \"%s\",", model); } ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]); ReplyToCommand(client, "}"); } else { if(!StrEqual(arg1, "cursor")) GetEntPropVector(client, Prop_Send, "m_vecOrigin", origin); GetEntPropVector(client, Prop_Send, "m_angRotation", angles); ReplyToCommand(client, "{"); ReplyToCommand(client, "\t\"type\": \"%s\",", arg1); ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]); ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]); ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]); ReplyToCommand(client, "}"); } return Plugin_Handled; } Action Command_RandomizerBuild(int client, int args) { char arg[64]; GetCmdArg(1, arg, sizeof(arg)); if(StrEqual(arg, "new")) { JSONObject temp = LoadMapJson(currentMap); GetCmdArg(2, arg, sizeof(arg)); if(temp != null && !StrEqual(arg, "confirm")) { delete temp; ReplyToCommand(client, "Existing map data found, enter /rbuild new confirm to overwrite."); return Plugin_Handled; } g_builder.Cleanup(); g_builder.mapData = new JSONObject(); SaveMapJson(currentMap, g_builder.mapData); ReplyToCommand(client, "Started new map data for %s", currentMap); } else if(StrEqual(arg, "load")) { if(args >= 2) { GetCmdArg(2, arg, sizeof(arg)); } else { strcopy(arg, sizeof(arg), currentMap); } g_builder.Cleanup(); g_builder.mapData = LoadMapJson(arg); if(g_builder.mapData != null) { ReplyToCommand(client, "Loaded map data for %s", arg); } else { ReplyToCommand(client, "No map data found for %s", arg); } } else if(StrEqual(arg, "menu")) { OpenMainMenu(client); } else if(g_builder.mapData == null) { ReplyToCommand(client, "No map data loaded 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 classname[128], model[128]; JSONObject entityData = new JSONObject(); GetEntityClassname(entity, classname, sizeof(classname)); if(StrContains(classname, "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("type", classname); entityData.SetString("model", model); } entityData.Set("origin", VecToArray(origin)); entityData.Set("angles", VecToArray(angles)); return entityData; } JSONObject ExportEntityInput(int entity, const char[] input) { char classname[128]; JSONObject entityData = new JSONObject(); GetEntityClassname(entity, classname, sizeof(classname)); int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID"); if(hammerid != 0) { entityData.SetInt("hammerid", hammerid); } else { char targetname[128]; GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); if(targetname[0] != '\0') { entityData.SetString("targetname", targetname); } else { entityData.SetString("classname", classname); } } entityData.SetString("input", input); return entityData; } bool OnSpawnerDone(int client, int entity, CompleteType result) { PrintToServer("Randomizer OnSpawnerDone"); if(result == Complete_PropSpawned && entity > 0) { JSONObject entityData = ExportEntity(entity, Export_Model); JSONArray entities = view_as(g_builder.selectedVariantData.Get("entities")); entities.Push(entityData); ReplyToCommand(client, "Added entity to variant"); RemoveEntity(entity); } return result == Complete_PropSpawned; } void OnSelectorDone(int client, ArrayList entities) { JSONArray entArray = view_as(g_builder.selectedVariantData.Get("entities")); JSONArray inputArray = g_builder.selectedVariantData.HasKey("inputs") ? view_as(g_builder.selectedVariantData.Get("inputs")) : null; if(entities != null) { JSONObject entityData; char classname[128]; for(int i = 0; i < entities.Length; i++) { int ref = entities.Get(i); GetEntityClassname(ref, classname, sizeof(classname)); if(StrEqual(classname, "func_simpleladder")) { if(inputArray == null) { inputArray = new JSONArray(); g_builder.selectedVariantData.Set("inputs", inputArray); } entityData = ExportEntityInput(ref, "_allow_ladder"); inputArray.Push(entityData); } else { entityData = ExportEntity(ref, Export_Model); entArray.Push(entityData); RemoveEntity(ref); } delete entityData; //? } PrintToChat(client, "Added %d entities to variant", entities.Length); delete entities; } } JSONArray VecToArray(float vec[3]) { JSONArray arr = new JSONArray(); arr.PushFloat(vec[0]); arr.PushFloat(vec[1]); arr.PushFloat(vec[2]); return arr; } void Command_RandomizerBuild_Scenes(int client, int args) { char arg[16]; GetCmdArg(2, arg, sizeof(arg)); if(StrEqual(arg, "new")) { if(args < 4) { ReplyToCommand(client, "Syntax: /rbuild scenes new "); } else { char name[64]; GetCmdArg(3, name, sizeof(name)); GetCmdArg(4, arg, sizeof(arg)); float chance = StringToFloat(arg); JSONObject scene = new JSONObject(); scene.SetFloat("chance", chance); scene.Set("variants", new JSONArray()); g_builder.mapData.Set(name, scene); g_builder.SelectScene(name); JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); JSONObject variantObj = new JSONObject(); variantObj.SetInt("weight", 1); variantObj.Set("entities", new JSONArray()); variants.Push(variantObj); g_builder.SelectVariant(0); ReplyToCommand(client, "Created & selected scene & variant %s#0", name); StartSelector(client, OnSelectorDone); } } else if(StrEqual(arg, "select") || StrEqual(arg, "load") || StrEqual(arg, "choose")) { GetCmdArg(3, arg, sizeof(arg)); if(g_builder.SelectScene(arg)) { int variantIndex; if(GetCmdArgIntEx(4, variantIndex)) { if(g_builder.SelectVariant(variantIndex)) { ReplyToCommand(client, "Selected scene: %s#%d", arg, variantIndex); } else { ReplyToCommand(client, "Unknown variant for scene"); } } else { ReplyToCommand(client, "Selected scene: %s", arg); } } else { ReplyToCommand(client, "No scene found"); } } else if(StrEqual(arg, "variants")) { Command_RandomizerBuild_Variants(client, args); } else if(args > 1) { ReplyToCommand(client, "Unknown argument, try: new, select, variants"); } else { ReplyToCommand(client, "Scenes:"); JSONObjectKeys iterator = g_builder.mapData.Keys(); while(iterator.ReadKey(arg, sizeof(arg))) { if(StrEqual(arg, g_builder.selectedSceneId)) { ReplyToCommand(client, "\t%s (selected)", arg); } else { ReplyToCommand(client, "\t%s", arg); } } } } void Command_RandomizerBuild_Variants(int client, int args) { if(g_builder.selectedSceneId[0] == '\0') { ReplyToCommand(client, "No scene selected, select with /rbuild groups select "); return; } char arg[16]; GetCmdArg(3, arg, sizeof(arg)); if(StrEqual(arg, "new")) { // /rbuild group variants new [weight] int weight; if(!GetCmdArgIntEx(4, weight)) { weight = 1; } JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); JSONObject variantObj = new JSONObject(); variantObj.SetInt("weight", weight); variantObj.Set("entities", new JSONArray()); int index = variants.Push(variantObj); g_builder.SelectVariant(index); ReplyToCommand(client, "Created variant #%d", index); } else if(StrEqual(arg, "select")) { int index = GetCmdArgInt(4); if(g_builder.SelectVariant(index)) { ReplyToCommand(client, "Selected variant: %s#%d", g_builder.selectedSceneId, index); } else { ReplyToCommand(client, "No variant found"); } } else { ReplyToCommand(client, "Variants:"); JSONObject variantObj; JSONArray variants = view_as(g_builder.selectedSceneData.Get("variants")); for(int i = 0; i < variants.Length; i++) { variantObj = view_as(variants.Get(i)); int weight = 1; if(variantObj.HasKey("weight")) weight = variantObj.GetInt("weight"); JSONArray entities = view_as(variantObj.Get("entities")); ReplyToCommand(client, " #%d. [W:%d] [#E:%d]", i, weight, entities.Length); } } } enum struct SceneData { char name[MAX_SCENE_NAME_LENGTH]; float chance; char group[MAX_SCENE_NAME_LENGTH]; ArrayList variants; void Cleanup() { g_MapData.activeScenes.Clear(); SceneVariantData choice; for(int i = 0; i < this.variants.Length; i++) { this.variants.GetArray(i, choice); choice.Cleanup(); } delete this.variants; } } enum struct SceneVariantData { int weight; ArrayList inputsList; ArrayList entities; ArrayList forcedScenes; void Cleanup() { delete this.inputsList; delete this.entities; delete this.forcedScenes; } } enum struct VariantEntityData { char type[32]; char model[128]; float origin[3]; float angles[3]; float scale[3]; int color[4]; } enum InputType { Input_Classname, Input_Targetname, Input_HammerId } enum struct VariantInputData { char name[MAX_INPUTS_CLASSNAME_LENGTH]; InputType type; char input[64]; void Trigger() { int entity = -1; switch(this.type) { case Input_Classname: { while((entity = FindEntityByClassname(entity, this.name)) != INVALID_ENT_REFERENCE) { this._trigger(entity); } } case Input_Targetname: { char targetname[64]; while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); if(StrEqual(targetname, this.name)) { this._trigger(entity); } } } case Input_HammerId: { int targetId = StringToInt(this.name); while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { int hammerId = GetEntProp(entity, Prop_Data, "m_iHammerID"); if(hammerId == targetId ) { this._trigger(entity); break; } } } } } void _trigger(int entity) { if(entity > 0 && IsValidEntity(entity)) { if(StrEqual(this.input, "_allow_ladder")) { if(HasEntProp(entity, Prop_Send, "m_iTeamNum")) { SetEntProp(entity, Prop_Send, "m_iTeamNum", 0); } else { Log("Warn: Entity (%d) with id \"%s\" has no teamnum for \"_allow_ladder\"", entity, this.name); } } else if(StrEqual(this.input, "_lock")) { AcceptEntityInput(entity, "Close"); AcceptEntityInput(entity, "Lock"); } else if(StrEqual(this.input, "_lock_nobreak")) { AcceptEntityInput(entity, "Close"); AcceptEntityInput(entity, "Lock"); AcceptEntityInput(entity, "SetUnbreakable"); } else { char cmd[32]; // Split input "a b" to a with variant "b" int len = SplitString(this.input, " ", cmd, sizeof(cmd)); if(len > -1) { SetVariantString(this.input[len]); AcceptEntityInput(entity, cmd); Debug("_trigger(%d): %s (v=%s)", entity, cmd, this.input[len]); } else { Debug("_trigger(%d): %s", entity, this.input); AcceptEntityInput(entity, this.input); } } } } } enum struct LumpEditData { char name[MAX_INPUTS_CLASSNAME_LENGTH]; InputType type; char action[32]; char value[64]; int _findLumpIndex(int startIndex = 0, EntityLumpEntry entry) { int length = EntityLump.Length(); char val[64]; Debug("Scanning for \"%s\" (type=%d)", this.name, this.type); for(int i = startIndex; i < length; i++) { entry = EntityLump.Get(i); int index = entry.FindKey("hammerid"); if(index != -1) { entry.Get(index, "", 0, val, sizeof(val)); if(StrEqual(val, this.name)) { return i; } } index = entry.FindKey("classname"); if(index != -1) { entry.Get(index, "", 0, val, sizeof(val)); Debug("%s vs %s", val, this.name); if(StrEqual(val, this.name)) { return i; } } index = entry.FindKey("targetname"); if(index != -1) { entry.Get(index, "", 0, val, sizeof(val)); if(StrEqual(val, this.name)) { return i; } } delete entry; } Log("Warn: Could not find any matching lump for \"%s\" (type=%d)", this.name, this.type); return -1; } void Trigger() { int index = 0; EntityLumpEntry entry; while((index = this._findLumpIndex(index, entry) != -1)) { // for(int i = 0; i < entry.Length; i++) { // entry.Get(i, a, sizeof(a), v, sizeof(v)); // Debug("%s=%s", a, v); // } this._trigger(entry); } } void _updateKey(EntityLumpEntry entry, const char[] key, const char[] value) { int index = entry.FindKey(key); if(index != -1) { Debug("update key %s = %s", key, value); entry.Update(index, key, value); } } void _trigger(EntityLumpEntry entry) { if(StrEqual(this.action, "setclassname")) { this._updateKey(entry, "classname", this.value); } delete entry; } } enum struct MapData { StringMap scenesKv; ArrayList scenes; ArrayList lumpEdits; ArrayList activeScenes; } enum loadFlags { FLAG_NONE = 0, FLAG_ALL_SCENES = 1, // Pick all scenes, no random chance FLAG_ALL_VARIANTS = 2, // Pick all variants (for debug purposes), FLAG_REFRESH = 4, // Load data bypassing cache FLAG_FORCE_ACTIVE = 8 // Similar to ALL_SCENES, bypasses % chance } // Reads (mapname).json file and parses it public JSONObject LoadMapJson(const char[] map) { Debug("Loading config for %s", map); char filePath[PLATFORM_MAX_PATH]; BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map); if(!FileExists(filePath)) { Log("[Randomizer] No map config file (data/randomizer/%s.json), not loading", map); return null; } JSONObject data = JSONObject.FromFile(filePath); if(data == null) { LogError("Could not parse map config file (data/randomizer/%s.json)", map); return null; } return data; } public void SaveMapJson(const char[] map, JSONObject json) { Debug("Saving config for %s", map); char filePath[PLATFORM_MAX_PATH], filePathTemp[PLATFORM_MAX_PATH]; BuildPath(Path_SM, filePathTemp, sizeof(filePath), "data/randomizer/%s.json.tmp", map); BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map); json.ToFile(filePathTemp, JSON_INDENT(4)); RenameFile(filePath, filePathTemp); SetFilePermissions(filePath, FPERM_U_WRITE | FPERM_U_READ | FPERM_G_WRITE | FPERM_G_READ | FPERM_O_READ); } public bool LoadMapData(const char[] map, int flags) { JSONObject data = LoadMapJson(map); if(data == null) { return false; } Debug("Starting parsing json data"); Cleanup(); g_MapData.scenes = new ArrayList(sizeof(SceneData)); g_MapData.scenesKv = new StringMap(); g_MapData.lumpEdits = new ArrayList(sizeof(LumpEditData)); g_MapData.activeScenes.Clear(); Profiler profiler = new Profiler(); profiler.Start(); JSONObjectKeys iterator = data.Keys(); char key[32]; while(iterator.ReadKey(key, sizeof(key))) { if(key[0] == '_') { if(StrEqual(key, "_lumps")) { JSONArray lumpsList = view_as(data.Get(key)); if(lumpsList != null) { for(int l = 0; l < lumpsList.Length; l++) { loadLumpData(g_MapData.lumpEdits, view_as(lumpsList.Get(l))); } } } else { Debug("Unknown special entry \"%s\", skipping", key); } } else { // if(data.GetType(key) != JSONType_Object) { // Debug("Invalid normal entry \"%s\" (not an object), skipping", key); // continue; // } JSONObject scene = view_as(data.Get(key)); // Parses scene data and inserts to scenes loadScene(key, scene); } } delete data; profiler.Stop(); Log("Parsed map file for %s(%d) and found %d scenes in %.4f seconds", map, flags, g_MapData.scenes.Length, profiler.Time); delete profiler; return true; } // Calls LoadMapData (read&parse (mapname).json) then select scenes public bool RunMap(const char[] map, int flags) { if(g_MapData.scenes == null || flags & view_as(FLAG_REFRESH)) { if(!LoadMapData(map, flags)) { return false; } } Profiler profiler = new Profiler(); profiler.Start(); selectScenes(flags); profiler.Stop(); Log("Done processing in %.4f seconds", g_MapData.scenes.Length, profiler.Time); return true; } void loadScene(const char key[MAX_SCENE_NAME_LENGTH], JSONObject sceneData) { SceneData scene; scene.name = key; scene.chance = sceneData.GetFloat("chance"); if(scene.chance < 0.0 || scene.chance > 1.0) { LogError("Scene \"%s\" has invalid chance (%f)", scene.name, scene.chance); return; } // TODO: load "entities", merge with choice.entities sceneData.GetString("group", scene.group, sizeof(scene.group)); scene.variants = new ArrayList(sizeof(SceneVariantData)); if(!sceneData.HasKey("variants")) { ThrowError("Failed to load: Scene \"%s\" has missing \"variants\" array", scene.name); return; } JSONArray entities; if(sceneData.HasKey("entities")) { entities = view_as(sceneData.Get("entities")); } JSONArray variants = view_as(sceneData.Get("variants")); for(int i = 0; i < variants.Length; i++) { // Parses choice and loads to scene.choices loadChoice(scene, view_as(variants.Get(i)), entities); } g_MapData.scenes.PushArray(scene); g_MapData.scenesKv.SetArray(scene.name, scene, sizeof(scene)); } void loadChoice(SceneData scene, JSONObject choiceData, JSONArray extraEntities) { SceneVariantData choice; choice.weight = 1; if(choiceData.HasKey("weight")) choice.weight = choiceData.GetInt("weight"); choice.entities = new ArrayList(sizeof(VariantEntityData)); choice.inputsList = new ArrayList(sizeof(VariantInputData)); choice.forcedScenes = new ArrayList(ByteCountToCells(MAX_SCENE_NAME_LENGTH)); // Load in any variant-based entities if(choiceData.HasKey("entities")) { JSONArray entities = view_as(choiceData.Get("entities")); for(int i = 0; i < entities.Length; i++) { // Parses entities and loads to choice.entities loadChoiceEntity(choice.entities, view_as(entities.Get(i))); } delete entities; } // Load in any entities that the scene has if(extraEntities != null) { for(int i = 0; i < extraEntities.Length; i++) { // Parses entities and loads to choice.entities loadChoiceEntity(choice.entities, view_as(extraEntities.Get(i))); } // delete extraEntities; } // Load all inputs if(choiceData.HasKey("inputs")) { JSONArray inputsList = view_as(choiceData.Get("inputs")); for(int i = 0; i < inputsList.Length; i++) { loadChoiceInput(choice.inputsList, view_as(inputsList.Get(i))); } delete inputsList; } if(choiceData.HasKey("force_scenes")) { JSONArray scenes = view_as(choiceData.Get("force_scenes")); char sceneId[32]; for(int i = 0; i < scenes.Length; i++) { scenes.GetString(i, sceneId, sizeof(sceneId)); choice.forcedScenes.PushString(sceneId); Debug("scene %s: require %s", scene.name, sceneId); } delete scenes; } scene.variants.PushArray(choice); } void loadChoiceInput(ArrayList list, JSONObject inputData) { VariantInputData input; input.type = Input_Classname; // Check classname -> targetname -> hammerid if(!inputData.GetString("classname", input.name, sizeof(input.name))) { if(inputData.GetString("targetname", input.name, sizeof(input.name))) { input.type = Input_Targetname; } else { if(inputData.GetString("hammerid", input.name, sizeof(input.name))) { input.type = Input_HammerId; } else { int id = inputData.GetInt("hammerid"); if(id > 0) { input.type = Input_HammerId; IntToString(id, input.name, sizeof(input.name)); } else { LogError("Missing valid input specification (hammerid, classname, targetname)"); return; } } } } inputData.GetString("input", input.input, sizeof(input.input)); list.PushArray(input); } void loadLumpData(ArrayList list, JSONObject inputData) { LumpEditData input; // Check classname -> targetname -> hammerid if(!inputData.GetString("classname", input.name, sizeof(input.name))) { if(inputData.GetString("targetname", input.name, sizeof(input.name))) { input.type = Input_Targetname; } else { if(inputData.GetString("hammerid", input.name, sizeof(input.name))) { input.type = Input_HammerId; } else { int id = inputData.GetInt("hammerid"); if(id > 0) { input.type = Input_HammerId; IntToString(id, input.name, sizeof(input.name)); } else { LogError("Missing valid input specification (hammerid, classname, targetname)"); return; } } } } inputData.GetString("action", input.action, sizeof(input.action)); inputData.GetString("value", input.value, sizeof(input.value)); list.PushArray(input); } void loadChoiceEntity(ArrayList list, JSONObject entityData) { VariantEntityData entity; entityData.GetString("model", entity.model, sizeof(entity.model)); if(!entityData.GetString("type", entity.type, sizeof(entity.type))) { entity.type = "prop_dynamic"; } /*else if(entity.type[0] == '_') { LogError("Invalid custom entity type \"%s\"", entity.type); return; }*/ GetVector(entityData, "origin", entity.origin); GetVector(entityData, "angles", entity.angles); GetVector(entityData, "scale", entity.scale); GetColor(entityData, "color", entity.color); list.PushArray(entity); } bool GetVector(JSONObject obj, const char[] key, float out[3]) { if(!obj.HasKey(key)) return false; JSONArray vecArray = view_as(obj.Get(key)); if(vecArray != null) { out[0] = vecArray.GetFloat(0); out[1] = vecArray.GetFloat(1); out[2] = vecArray.GetFloat(2); } return true; } void GetColor(JSONObject obj, const char[] key, int out[4], int defaultColor[4] = { 255, 255, 255, 255 }) { if(obj.HasKey(key)) { JSONArray vecArray = view_as(obj.Get(key)); out[0] = vecArray.GetInt(0); out[1] = vecArray.GetInt(1); out[2] = vecArray.GetInt(2); if(vecArray.Length == 4) out[3] = vecArray.GetInt(3); else out[3] = 255; } else { out = defaultColor; } } void selectScenes(int flags = 0) { SceneData scene; StringMap groups = new StringMap(); ArrayList list; // Select and spawn non-group scenes // TODO: refactor to use .scenesKv for(int i = 0; i < g_MapData.scenes.Length; i++) { g_MapData.scenes.GetArray(i, scene); // TODO: Exclusions // Select scene if not in group, or add to list of groups if(scene.group[0] == '\0') { selectScene(scene, flags); } else { // Load it into group list if(!groups.GetValue(scene.group, list)) { list = new ArrayList(); } list.Push(i); groups.SetValue(scene.group, list); } } // Iterate through groups and select a random scene: StringMapSnapshot snapshot = groups.Snapshot(); char key[MAX_SCENE_NAME_LENGTH]; for(int i = 0; i < snapshot.Length; i++) { snapshot.GetKey(i, key, sizeof(key)); groups.GetValue(key, list); // Select a random scene from the group: int index = GetURandomInt() % list.Length; index = list.Get(index); g_MapData.scenes.GetArray(index, scene); Debug("Selected scene \"%s\" for group %s (%d members)", scene.name, key, list.Length); selectScene(scene, flags); delete list; } // Traverse active scenes, loading any other scene it requires (via .force_scenes) ActiveSceneData aScene; SceneVariantData choice; char sceneId[64]; // list of scenes that will need to be forced if not already: 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); // Load scene from active scene entry if(!g_MapData.scenesKv.GetArray(aScene.name, scene, sizeof(scene))) { // can't find scene, ignore continue; } if(aScene.variantIndex < scene.variants.Length) { 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); } } } } if(forcedScenes.Length > 0) { Debug("Loading %d forced scenes", forcedScenes.Length); } // 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; j++) { g_MapData.activeScenes.GetArray(j, aScene); if(StrEqual(aScene.name, key)) { isSceneAlreadyLoaded = true; break; } } if(isSceneAlreadyLoaded) continue; g_MapData.scenesKv.GetArray(key, scene, sizeof(scene)); selectScene(scene, flags | view_as(FLAG_FORCE_ACTIVE)); } delete forcedScenes; delete snapshot; delete groups; } void selectScene(SceneData scene, int flags) { // Use the .chance field unless FLAG_ALL_SCENES or FLAG_FORCE_ACTIVE is set if(~flags & view_as(FLAG_ALL_SCENES) && ~flags & view_as(FLAG_FORCE_ACTIVE) && GetURandomFloat() > scene.chance) { return; } if(scene.variants.Length == 0) { LogError("Warn: No variants were found for scene \"%s\"", scene.name); return; } ArrayList choices = new ArrayList(); SceneVariantData choice; int index; Debug("Scene %s has %d variants", scene.name, scene.variants.Length); // Weighted random: Push N times dependent on weight for(int i = 0; i < scene.variants.Length; i++) { scene.variants.GetArray(i, choice); if(flags & view_as(FLAG_ALL_VARIANTS)) { spawnVariant(choice); } else { if(choice.weight <= 0) { PrintToServer("Warn: Variant %d in scene %s has invalid weight", i, scene.name); continue; } for(int c = 0; c < choice.weight; c++) { choices.Push(i); } } } if(flags & view_as(FLAG_ALL_VARIANTS)) { delete choices; } else if(choices.Length > 0) { index = GetURandomInt() % choices.Length; index = choices.Get(index); delete choices; Log("Spawned scene \"%s\" with variant #%d", scene.name, index); scene.variants.GetArray(index, choice); spawnVariant(choice); } ActiveSceneData aScene; strcopy(aScene.name, sizeof(aScene.name), scene.name); aScene.variantIndex = index; g_MapData.activeScenes.PushArray(aScene); } void spawnVariant(SceneVariantData choice) { VariantEntityData entity; for(int i = 0; i < choice.entities.Length; i++) { choice.entities.GetArray(i, entity); spawnEntity(entity); } if(choice.inputsList.Length > 0) { VariantInputData input; for(int i = 0; i < choice.inputsList.Length; i++) { choice.inputsList.GetArray(i, input); input.Trigger(); } } } int CreateLight(const float origin[3], const float angles[3], const int color[4] = { 255, 255, 255, 1 }, float distance = 100.0) { int entity = CreateEntityByName("light_dynamic"); if(entity == -1) return -1; DispatchKeyValue(entity, "targetname", ENT_PROP_NAME); DispatchKeyValueInt(entity, "brightness", color[3]); DispatchKeyValueFloat(entity, "distance", distance); DispatchKeyValueFloat(entity, "_inner_cone", angles[0]); DispatchKeyValueFloat(entity, "_cone", angles[1]); DispatchKeyValueFloat(entity, "pitch", angles[2]); // DispatchKeyValueInt() TeleportEntity(entity, origin, NULL_VECTOR, NULL_VECTOR); if(!DispatchSpawn(entity)) return -1; SetEntityRenderColor(entity, color[0], color[1], color[2], color[3]); AcceptEntityInput(entity, "TurnOn"); return entity; } void spawnEntity(VariantEntityData entity) { if(StrEqual(entity.type, "env_fire")) { Debug("spawning \"%s\" at (%.1f %.1f %.1f) rot (%.0f %.0f %.0f)", entity.type, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); CreateFire(entity.origin, 20.0, 100.0, 1.0); } else if(StrEqual(entity.type, "light_dynamic")) { CreateLight(entity.origin, entity.angles, entity.color, entity.scale[0]); Effect_DrawBeamBoxRotatableToAll(entity.origin, { -5.0, -5.0, -5.0}, { 5.0, 5.0, 5.0}, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 40.0, 0.1, 0.1, 0, 0.0, {255, 255, 0, 255}, 0); } else if(StrEqual(entity.type, "env_physics_blocker") || StrEqual(entity.type, "env_player_blocker")) { CreateEnvBlockerScaled(entity.type, entity.origin, entity.scale); } else if(StrEqual(entity.type, "infodecal")) { CreateDecal(entity.model, entity.origin); } else if(StrContains(entity.type, "prop_") == 0 || StrEqual(entity.type, "prop_fuel_barrel")) { if(entity.model[0] == '\0') { LogError("Missing model for entity with type \"%s\"", entity.type); return; } PrecacheModel(entity.model); int prop = CreateProp(entity.type, entity.model, entity.origin, entity.angles); SetEntityRenderColor(prop, entity.color[0], entity.color[1], entity.color[2], entity.color[3]); } else if(StrEqual(entity.type, "hammerid")) { int targetId = StringToInt(entity.model); if(targetId > 0) { int ent = -1; while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) { int hammerId = GetEntProp(ent, Prop_Data, "m_iHammerID"); if(hammerId == targetId) { Debug("moved entity (hammerid=%d) to %.0f %.0f %.0f rot %.0f %.0f %.0f", targetId, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); return; } } } Debug("Warn: Could not find entity (hammerid=%d) (model=%s)", targetId, entity.model); } else if(StrEqual(entity.type, "targetname")) { int ent = -1; char targetname[64]; bool found = false; while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) { GetEntPropString(ent, Prop_Data, "m_iName", targetname, sizeof(targetname)); if(StrEqual(entity.model, targetname)) { Debug("moved entity (targetname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); found = true; } } if(!found) Debug("Warn: Could not find entity (targetname=%s)", entity.model); } else if(StrEqual(entity.type, "classname")) { int ent = -1; char classname[64]; bool found; while((ent = FindEntityByClassname(ent, classname)) != INVALID_ENT_REFERENCE) { Debug("moved entity (classname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]); TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR); found = true; } if(!found) Debug("Warn: Could not find entity (classname=%s)", entity.model); } else if(StrContains(entity.type, "_car") != -1) { SpawnCar(entity); } else { LogError("Unknown entity type \"%s\"", entity.type); } } void Debug(const char[] format, any ...) { #if defined DEBUG_SCENE_PARSE char buffer[192]; VFormat(buffer, sizeof(buffer), format, 2); PrintToServer("[Randomizer::Debug] %s", buffer); PrintToConsoleAll("[Randomizer::Debug] %s", buffer); #endif } void Log(const char[] format, any ...) { char buffer[192]; VFormat(buffer, sizeof(buffer), format, 2); PrintToServer("[Randomizer] %s", buffer); } void Cleanup() { if(g_MapData.scenes != null) { SceneData scene; for(int i = 0; i < g_MapData.scenes.Length; i++) { g_MapData.scenes.GetArray(i, scene); scene.Cleanup(); } delete g_MapData.scenes; } delete g_MapData.lumpEdits; // Cleanup all alarm car entities: int entity = -1; char targetname[128]; while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) { GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname)); if(StrContains(targetname, "randomizer_car") != -1) { RemoveEntity(entity); } } // TODO: delete car alarms DeleteCustomEnts(); g_MapData.activeScenes.Clear(); }