sourcemod-plugins/scripting/l4d2_randomizer.sp

1615 lines
No EOL
55 KiB
SourcePawn

#pragma semicolon 1
#pragma newdecls required
//#define DEBUG
#define PLUGIN_VERSION "1.0"
#define DEBUG_SCENE_PARSE 1
#define DEBUG_BLOCKERS 1
#include <sourcemod>
#include <sdktools>
//#include <sdkhooks>
#include <profiler>
// #include <json>
#include <ripext>
#include <jutils>
#include <entitylump>
#undef REQUIRE_PLUGIN
#include <editor>
int g_iLaserIndex;
#if defined DEBUG_BLOCKERS
#include <smlib/effects>
#endif
#define ENT_PROP_NAME "randomizer"
#define ENT_ENV_NAME "randomizer"
#define ENT_BLOCKER_NAME "randomizer"
#include <gamemodes/ents>
#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];
static int _ropeIndex;
enum struct GascanSpawnerData {
float origin[3];
float angles[3];
}
ArrayList g_gascanRespawnQueue;
AnyMap g_gascanSpawners;
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) {
JSONObject entityData = ExportEntity(entity, exportType);
this.AddEntityData(entityData);
}
void AddEntityData(JSONObject entityData) {
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"));
}
entities.Push(entityData);
}
}
#include <randomizer/rbuild.sp>
#include <randomizer/caralarm.sp>
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_RoundStartPostNav);
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));
g_gascanSpawners = new AnyMap();
}
bool randomizerRan = false;
void Event_PlayerFirstSpawn(Event event, const char[] name ,bool dontBroadcast) {
if(!randomizerRan) {
CreateTimer(0.1, Timer_Run);
randomizerRan = true;
}
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_RoundStartPostNav(Event event, const char[] name ,bool dontBroadcast) {
// if(cvarEnabled.BoolValue)
// CreateTimer(15.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() {
randomizerRan = false;
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() {
}
public void OnEntityCreated(int entity, const char[] classname) {
if(StrEqual(classname, "weapon_gascan")) {
RequestFrame(Frame_RandomizeGascan, entity);
}
}
void Frame_RandomizeGascan(int gascan) {
if(!IsValidEntity(gascan)) return;
if(g_gascanRespawnQueue == null || g_gascanRespawnQueue.Length == 0) return;
GascanSpawnerData spawner;
g_gascanRespawnQueue.GetArray(0, spawner);
g_gascanRespawnQueue.Erase(0);
AssignGascan(gascan, spawner);
}
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<int>(FLAG_REFRESH));
}
if(client > 0)
PrintCenterText(client, "Cycled flags=%d", flags);
} else {
if(g_MapData.activeScenes == null) {
ReplyToCommand(client, "No map data");
return Plugin_Handled;
}
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);
if(entity == 0) {
ReplyToCommand(client, "No entity found");
return Plugin_Handled;
}
GetCmdArg(2, arg1, sizeof(arg1));
ExportType exportType = Export_Model;
if(StrEqual(arg1, "hammerid")) {
exportType = Export_HammerId;
ReplyToCommand(client, "Added entity's hammerid to variant #%d", g_builder.selectedVariantIndex);
} else if(StrEqual(arg1, "targetname")) {
ReplyToCommand(client, "Added entity's targetname to variant #%d", g_builder.selectedVariantIndex);
exportType = Export_TargetName;
} else {
ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex);
}
g_builder.AddEntity(entity, exportType);
} else if(StrEqual(arg, "entityid")) {
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 = GetCmdArgInt(2);
if(entity <= 0 && !IsValidEntity(entity)) {
ReplyToCommand(client, "No entity found");
return Plugin_Handled;
}
GetCmdArg(2, arg1, sizeof(arg1));
ExportType exportType = Export_Model;
if(StrEqual(arg1, "hammerid")) {
exportType = Export_HammerId;
ReplyToCommand(client, "Added entity's hammerid to variant #%d", g_builder.selectedVariantIndex);
} else if(StrEqual(arg1, "targetname")) {
ReplyToCommand(client, "Added entity's targetname to variant #%d", g_builder.selectedVariantIndex);
exportType = Export_TargetName;
} else {
ReplyToCommand(client, "Added entity #%d to variant #%d", entity, g_builder.selectedVariantIndex);
}
g_builder.AddEntity(entity, exportType);
} else if(StrEqual(arg, "decal")) {
if(g_builder.selectedVariantData == null) {
ReplyToCommand(client, "Please load map data, select a scene and a variant.");
return Plugin_Handled;
}
float pos[3];
GetLookingPosition(client, Filter_IgnorePlayer, pos);
Effect_DrawBeamBoxRotatableToAll(pos, { -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, {73, 0, 130, 255}, 0);
JSONObject obj = new JSONObject();
obj.SetString("type", "infodecal");
obj.Set("origin", VecToArray(pos));
obj.SetString("model", "decals/checkpointarrow01_black.vmt");
g_builder.AddEntityData(obj);
ReplyToCommand(client, "Added sprite to variant #%d", g_builder.selectedVariantIndex);
} else if(StrEqual(arg, "fire")) {
if(g_builder.selectedVariantData == null) {
ReplyToCommand(client, "Please load map data, select a scene and a variant.");
return Plugin_Handled;
}
float pos[3];
GetLookingPosition(client, Filter_IgnorePlayer, pos);
JSONObject obj = new JSONObject();
obj.SetString("type", "env_fire");
obj.Set("origin", VecToArray(pos));
g_builder.AddEntityData(obj);
ReplyToCommand(client, "Added fire to variant #%d", g_builder.selectedVariantIndex);
} else if(StrEqual(arg, "light")) {
if(g_builder.selectedVariantData == null) {
ReplyToCommand(client, "Please load map data, select a scene and a variant.");
return Plugin_Handled;
}
float pos[3];
int defaultColor[4] = { 255, 255, 255, 255};
float empty[3];
float scale[3] = { 100.0, -1.0, -1.0 };
GetLookingPosition(client, Filter_IgnorePlayer, pos);
JSONObject obj = new JSONObject();
obj.SetString("type", "light_dynamic");
obj.Set("origin", FromFloatArray(pos, 3));
obj.Set("color", FromIntArray(defaultColor, 4));
obj.Set("angles", FromFloatArray(empty, 3));
obj.Set("scale", FromFloatArray(scale, 3));
g_builder.AddEntityData(obj);
ReplyToCommand(client, "Added light to variant #%d", g_builder.selectedVariantIndex);
} else if(StrEqual(arg, "wall")) {
if(g_builder.selectedVariantData == null) {
ReplyToCommand(client, "Please load map data, select a scene and a variant.");
return Plugin_Handled;
}
float pos[3];
float scale[3] = { 15.0, 30.0, 100.0 };
GetLookingPosition(client, Filter_IgnorePlayer, pos);
JSONObject obj = new JSONObject();
obj.SetString("type", "env_player_blocker");
obj.Set("origin", FromFloatArray(pos, 3));
obj.Set("scale", FromFloatArray(scale, 3));
g_builder.AddEntityData(obj);
ReplyToCommand(client, "Added wall to variant #%d", g_builder.selectedVariantIndex);
} else if(StrEqual(arg, "gascan")) {
if(g_builder.selectedVariantData == null) {
ReplyToCommand(client, "Please load map data, select a scene and a variant.");
return Plugin_Handled;
}
float pos[3];
float ang[3];
int entity = GetLookingPosition(client, Filter_IgnorePlayer, pos);
if(entity == 0) {
GetClientAbsOrigin(client, pos);
pos[2] += 10.0;
GetClientEyeAngles(client, ang);
} else {
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", pos);
GetEntPropVector(entity, Prop_Send, "m_angRotation", ang);
}
JSONObject obj = new JSONObject();
obj.SetString("type", "_gascan");
obj.Set("origin", FromFloatArray(pos, 3));
obj.Set("angles", FromFloatArray(ang, 3));
g_builder.AddEntityData(obj);
ReplyToCommand(client, "Added gascan (%d) to variant #%d", entity, g_builder.selectedVariantIndex);
} 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;
}
public void L4D2_CGasCan_EventKilled_Post(int gascan, int inflictor, int attacker) {
GascanSpawnerData spawner;
// If Gascan was destroyed, and was from a spawner
if(g_gascanSpawners.GetArray(gascan, spawner, sizeof(spawner))) {
g_gascanSpawners.Remove(gascan);
// Push to queue, so when it respawns it can pop it off
if(g_gascanRespawnQueue == null) {
g_gascanRespawnQueue = new ArrayList(sizeof(GascanSpawnerData));
}
g_gascanRespawnQueue.PushArray(spawner, sizeof(spawner));
Debug("gascan %d destroyed. queue size=%d", gascan, g_gascanRespawnQueue.Length);
}
}
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"));
JSONArray inputArray = g_builder.selectedVariantData.HasKey("inputs") ? view_as<JSONArray>(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 {
// If there is a hammerid (> 0), then it's built on the map - we don't want to delete it
// If it is 0, it was spawned, probably by prop spawner, so we remove it
int hammerId = GetEntProp(ref, Prop_Data, "m_iHammerID");
entityData = ExportEntity(ref, hammerId > 0 ? Export_HammerId : Export_Model);
entArray.Push(entityData);
if(hammerId == 0)
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;
}
JSONArray FromFloatArray(float[] vec, int count) {
JSONArray arr = new JSONArray();
for(int i = 0 ; i < count; i++) {
arr.PushFloat(vec[i]);
}
return arr;
}
JSONArray FromIntArray(int[] vec, int count) {
JSONArray arr = new JSONArray();
for(int i = 0 ; i < count; i++) {
arr.PushInt(vec[i]);
}
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 {
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];
char targetname[128];
float origin[3];
float angles[3];
float scale[3];
int color[4];
ArrayList keyframes;
}
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];
int count = 0;
while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) {
GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname));
if(StrEqual(targetname, this.name)) {
this._trigger(entity);
count++;
}
}
if(count == 0) {
PrintToServer("[Randomizer::WARN] Input TargetName=\"%s\" matched 0 entties", this.name);
}
}
case Input_HammerId: {
int targetId = StringToInt(this.name);
int count = 0;
while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) {
int hammerId = GetEntProp(entity, Prop_Data, "m_iHammerID");
if(hammerId == targetId ) {
this._trigger(entity);
count++;
break;
}
}
if(count == 0) {
PrintToServer("[Randomizer::WARN] Input HammerId=%d matched 0 entties", targetId);
}
}
}
}
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;
ArrayList gascanSpawners;
}
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<JSONArray>(data.Get(key));
if(lumpsList != null) {
for(int l = 0; l < lumpsList.Length; l++) {
loadLumpData(g_MapData.lumpEdits, view_as<JSONObject>(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<JSONObject>(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<int>(FLAG_REFRESH)) {
if(!LoadMapData(map, flags)) {
return false;
}
}
Profiler profiler = new Profiler();
profiler.Start();
selectScenes(flags);
spawnGascans();
profiler.Stop();
_ropeIndex = 0;
Log("Done processing in %.4f seconds", g_MapData.scenes.Length, profiler.Time);
return true;
}
void spawnGascans() {
if(g_MapData.gascanSpawners != null && g_MapData.gascanSpawners.Length > 0) {
// Iterate through every gascan until we run out - picking a random spawner each time
int entity = -1;
char targetname[9];
GascanSpawnerData spawner;
int spawnerCount = g_MapData.gascanSpawners.Length;
int count;
while((entity = FindEntityByClassname(entity, "weapon_gascan")) != INVALID_ENT_REFERENCE) {
GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname));
int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID");
int glowColor = GetEntProp(entity, Prop_Send, "m_glowColorOverride"); // check if white
if(hammerid == 0 && glowColor == 16777215 && targetname[0] == '\0' && !g_gascanSpawners.ContainsKey(entity)) {
// Found a valid gascan, apply a random spawner
int spawnerIndex = GetRandomInt(0, g_MapData.gascanSpawners.Length - 1);
g_MapData.gascanSpawners.GetArray(spawnerIndex, spawner);
g_MapData.gascanSpawners.Erase(spawnerIndex); // only want one can to use this spawner
AssignGascan(entity, spawner);
count++;
}
}
Debug("Assigned %d gascans to %d spawners", count, spawnerCount);
}
}
void AssignGascan(int gascan, GascanSpawnerData spawner) {
g_gascanSpawners.SetArray(gascan, spawner, sizeof(spawner));
TeleportEntity(gascan, spawner.origin, spawner.angles, NULL_VECTOR);
Debug("Assigning gascan %d to spawner at %.0f %.0f %.0f", gascan, spawner.origin[0], spawner.origin[1], spawner.origin[2]);
}
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<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, 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, 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<JSONArray>(choiceData.Get("entities"));
for(int i = 0; i < entities.Length; i++) {
// Parses entities and loads to choice.entities
loadChoiceEntity(choice.entities, view_as<JSONObject>(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<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);
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("targetname", entity.targetname, sizeof(entity.targetname))) {
Format(entity.targetname, sizeof(entity.targetname), "randomizer_%s", entity.targetname);
}
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;
}*/
if(StrEqual(entity.type, "move_rope")) {
if(!entityData.HasKey("keyframes")) {
LogError("move_rope entity is missing keyframes: Vec[] property");
return;
}
entity.keyframes = new ArrayList(3);
JSONArray keyframesData = view_as<JSONArray>(entityData.Get("keyframes"));
float vec[3];
for(int i = 0 ; i < keyframesData.Length; i++) {
JSONArray vecArray = view_as<JSONArray>(keyframesData.Get(i));
vec[0] = vecArray.GetFloat(0);
vec[1] = vecArray.GetFloat(1);
vec[2] = vecArray.GetFloat(2);
entity.keyframes.PushArray(vec);
}
}
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<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(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);
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<int>(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<int>(FLAG_ALL_SCENES) && ~flags & view_as<int>(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<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);
}
}
}
if(flags & view_as<int>(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 AddGascanSpawner(VariantEntityData data) {
if(g_MapData.gascanSpawners == null) {
g_MapData.gascanSpawners = new ArrayList(sizeof(GascanSpawnerData));
}
GascanSpawnerData spawner;
spawner.origin = data.origin;
spawner.angles = data.angles;
g_MapData.gascanSpawners.PushArray(spawner);
// Debug("Added gascan spawner at %.0f %.0f %.0f", spawner.origin[0], spawner.origin[1], spawner.origin[2]);
}
void spawnEntity(VariantEntityData entity) {
if(StrEqual(entity.type, "_gascan")) {
AddGascanSpawner(entity);
} else 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")) {
Effect_DrawBeamBoxRotatableToAll(entity.origin, { -1.0, -5.0, -5.0}, { 1.0, 5.0, 5.0}, NULL_VECTOR, g_iLaserIndex, 0, 0, 0, 40.0, 0.1, 0.1, 0, 0.0, {73, 0, 130, 255}, 0);
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;
} else if(!PrecacheModel(entity.model)) {
LogError("Precache of entity model \"%s\" with type \"%s\" failed", entity.model, entity.type);
return;
}
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 if(StrEqual(entity.type, "move_rope")) {
if(!PrecacheModel(entity.model)) {
LogError("Precache of entity model \"%s\" with type \"%s\" failed", entity.model, entity.type);
return;
} else if(entity.keyframes == null) {
// should not happen
LogError("rope entity has no keyframes", entity.keyframes);
return;
}
CreateRope(entity);
} else {
LogError("Unknown entity type \"%s\"", entity.type);
}
}
int CreateRope(VariantEntityData data) {
char targetName[32], nextKey[32];
Format(targetName, sizeof(targetName), "randomizer_rope%d", _ropeIndex);
Format(nextKey, sizeof(nextKey), "randomizer_rope%d_0", _ropeIndex);
int entity = _CreateRope("move_rope", targetName, nextKey, data.model, data.origin);
float pos[3];
for(int i = 0; i < data.keyframes.Length; i++) {
nextKey[0] = '\0';
Format(targetName, sizeof(targetName), "randomizer_rope%d_%d", _ropeIndex, i);
if(i < data.keyframes.Length - 1) {
Format(nextKey, sizeof(nextKey), "randomizer_rope%d_%d", _ropeIndex, i + 1);
}
data.keyframes.GetArray(i, pos, sizeof(pos));
_CreateRope("move_rope", targetName, nextKey, data.model, pos);
}
Debug("created rope #%d with %d keyframes. entid:%d", _ropeIndex, data.keyframes.Length, entity);
_ropeIndex++;
return entity;
}
int _CreateRope(const char[] type, const char[] targetname, const char[] nextKey, const char[] texture, const float origin[3]) {
int entity = CreateEntityByName(type);
if(entity == -1) return -1;
Debug("_createRope(\"%s\", \"%s\", \"%s\", \"%s\", %.0f %.0f %.0f", type, targetname, nextKey, texture, origin[0], origin[1], origin[2]);
DispatchKeyValue(entity, "targetname", targetname);
DispatchKeyValue(entity, "NextKey", nextKey);
DispatchKeyValue(entity, "RopeMaterial", texture);
DispatchKeyValueInt(entity, "Type", 0);
DispatchKeyValueFloat(entity, "Width", 2.0);
DispatchKeyValueInt(entity, "Breakable", 0);
DispatchKeyValueInt(entity, "Slack", 0);
DispatchKeyValueInt(entity, "Type", 0);
DispatchKeyValueInt(entity, "TextureScale", 2);
DispatchKeyValueInt(entity, "Subdiv", 2);
DispatchKeyValueInt(entity, "MoveSpeed", 0);
DispatchKeyValueInt(entity, "Dangling", 0);
DispatchKeyValueInt(entity, "Collide", 0);
DispatchKeyValueInt(entity, "Barbed", 0);
DispatchKeyValue(entity, "PositionInterpolator", "2");
// DispatchKeyValueFloat( entity, "m_RopeLength", 10.0 );
TeleportEntity(entity, origin, NULL_VECTOR, NULL_VECTOR);
if(!DispatchSpawn(entity)) {
return -1;
}
return entity;
}
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;
delete g_MapData.gascanSpawners;
// Cleanup all alarm car entities:
int entity = -1;
char targetname[128];
while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) {
if(!IsValidEntity(entity)) continue;
GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname));
if(StrContains(targetname, "randomizer_") != -1) {
RemoveEntity(entity);
}
}
// TODO: delete car alarms
DeleteCustomEnts();
g_MapData.activeScenes.Clear();
g_gascanSpawners.Clear();
delete g_gascanRespawnQueue;
}