sourcemod-plugins/scripting/l4d2_randomizer.sp
2025-02-01 15:14:06 -06:00

918 lines
No EOL
34 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>
#include <randomizer/defs.sp>
#if defined DEBUG_BLOCKERS
#include <smlib/effects>
#endif
#include <smlib/strings>
#define ENT_PROP_NAME "randomizer"
#define ENT_ENV_NAME "randomizer"
#define ENT_BLOCKER_NAME "randomizer"
#include <gamemodes/ents>
ConVar cvarEnabled; // is map enabled
char currentMap[64];
bool randomizerRan = false;
#include <randomizer/util.sp>
#include <randomizer/loader_functions.sp>
#include <randomizer/select_functions.sp>
#include <randomizer/spawn_functions.sp>
#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.");
}
RegAdminCmd("sm_rcycle", Command_CycleRandom, ADMFLAG_CHEATS);
RegAdminCmd("sm_expent", Command_ExportEnt, ADMFLAG_GENERIC);
RegAdminCmd("sm_rbuild", Command_RandomizerBuild, ADMFLAG_CHEATS);
RegAdminCmd("sm_randomizer", Command_Debug, ADMFLAG_GENERIC);
cvarEnabled = CreateConVar("sm_randomizer_enabled", "0");
HookEvent("player_first_spawn", Event_PlayerFirstSpawn);
HookEvent("game_end", Event_GameEnd);
InitGlobals();
}
void Event_GameEnd(Event event, const char[] name ,bool dontBroadcast) {
// Purge the traverse stack after a campaign is played
ClearTraverseStack();
}
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.");
}
}
public void OnMapStart() {
g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt", true);
GetCurrentMap(currentMap, sizeof(currentMap));
// We wait a while before running to prevent some edge cases i don't remember
}
public void OnMapEnd() {
randomizerRan = false;
g_builder.Cleanup();
// For maps that players traverse backwards, like hard rain (c4m1_milltown_a -> c4m3_milltown_b )
// We store the selection of the _a map, to later be loaded for _b maps
// This is done at end of map just in case a user re-runs the cycle and generates a different selection
if(g_selection != null) {
if(IsTraverseMapA(currentMap)) {
Log("Storing %s in map traversal store", currentMap);
StoreTraverseSelection(currentMap, g_selection);
}
// We want to store milltown_a twice, so the c4m5_milltown_escape can also pop it off
if(StrEqual(currentMap, "c4m1_milltown_a")) {
StoreTraverseSelection(currentMap, g_selection);
}
}
// don't clear entities because they will be deleted anyway (and errors if you tryq)
Cleanup(false);
}
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) {
LoadRunGlobalMap(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;
}
Action Command_Debug(int client, int args) {
// TODO is builder active, selection active, list of traverse pool, can pool
if(args > 0) {
char arg[32];
GetCmdArg(1, arg, sizeof(arg));
if(StrEqual(arg, "scenes")) {
if(g_MapData.IsLoaded()) {
StringMapSnapshot snapshot = g_MapData.scenesKv.Snapshot();
char buffer[MAX_SCENE_NAME_LENGTH];
for(int i = 0; i < snapshot.Length; i++) {
snapshot.GetKey(i, buffer, sizeof(buffer));
ReplyToCommand(client, "%d. %s", i, buffer);
}
delete snapshot;
} else {
ReplyToCommand(client, "No map data loaded");
}
} if(StrEqual(arg, "traverse")) {
TraverseData trav;
for(int i = 0; i < g_mapTraverseSelectionStack.Length; i++) {
g_mapTraverseSelectionStack.GetArray(i, trav, sizeof(trav));
if(trav.selection == null) {
ReplyToCommand(client, " #%d - %s: ERROR", i, trav.map);
} else {
ReplyToCommand(client, " #%d - %s: %d scenes", i, trav.map, trav.selection.Length);
}
}
} else if(StrEqual(arg, "store")) {
char buffer[64];
if(args == 1) {
strcopy(buffer, sizeof(buffer), currentMap);
} else {
GetCmdArg(2, buffer, sizeof(buffer));
}
StoreTraverseSelection(buffer, g_selection);
ReplyToCommand(client, "Stored current selection as %s", buffer);
} /*else if(StrEqual(arg, "identify")) {
if(args == 1) {
ReplyToCommand(client, "Specify scene name");
} else if(!g_MapData.IsLoaded()) {
ReplyToCommand(client, "No map data loaded");
} else {
GetCmdArg(2, arg, sizeof(arg));
SceneData scene;
g_MapData.sceneKv.GetArray()
}
}*/
else {
ReplyToCommand(client, "unknown subcommand");
}
return Plugin_Handled;
}
ReplyToCommand(client, "Enabled: %b", cvarEnabled.BoolValue);
ReplyToCommand(client, "Map Data: %s", g_MapData.IsLoaded() ? "Loaded" : "-");
if(g_selection != null) {
ReplyToCommand(client, "Scene Selection: %d selected", g_selection.Count);
} else {
ReplyToCommand(client, "Scene Selection: -");
}
ReplyToCommand(client, "Builder Data: %s", g_builder.IsLoaded() ? "Loaded" : "-");
ReplyToCommand(client, "Traverse Store: count=%d", g_mapTraverseSelectionStack.Length);
if(g_gascanRespawnQueue != null) {
ReplyToCommand(client, "Gascan Spawners: count=%d queue_size=%d", g_gascanSpawners.Size, g_gascanRespawnQueue.Length);
} else {
ReplyToCommand(client, "Gascan Spawners: count=%d queue_size=-", g_gascanSpawners.Size);
}
return Plugin_Handled;
}
public Action Command_CycleRandom(int client, int args) {
if(args > 0) {
DeleteCustomEnts();
int flags = GetCmdArgInt(1);
if(flags < 0) {
ReplyToCommand(client, "Invalid flags");
} else {
if(args > 1) {
char buffer[64];
GetCmdArg(2, buffer, sizeof(buffer));
LoadRunGlobalMap(buffer, flags | view_as<int>(FLAG_REFRESH));
PrintCenterText(client, "Cycled %s flags=%d", buffer, flags);
} else {
LoadRunGlobalMap(currentMap, flags | view_as<int>(FLAG_REFRESH));
}
if(client > 0)
PrintCenterText(client, "Cycled flags=%d", flags);
}
} else {
if(g_selection == null) {
ReplyToCommand(client, "No map selection active");
return Plugin_Handled;
}
ReplyToCommand(client, "Active Scenes (%d/%d):", g_selection.Count, g_MapData.scenes.Length);
SelectedSceneData aScene;
char buffer[32];
for(int i = 0; i < g_selection.Count; i++) {
g_selection.Get(i, aScene);
buffer[0] = '\0';
for(int v = 0; v < aScene.selectedVariantIndexes.Length; v++) {
int index = aScene.selectedVariantIndexes.Get(v);
Format(buffer, sizeof(buffer), "#%d ", index);
}
ReplyToCommand(client, "\t%s: variants %s", aScene.name, buffer);
}
}
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;
}
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", FromFloatArray(pos, 3));
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", FromFloatArray(pos, 3));
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 };
GetClientAbsOrigin(client, 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", FromFloatArray(size, 3));
}
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", FromFloatArray(origin, 3));
entityData.Set("angles", FromFloatArray(angles, 3));
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;
}
}
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) {
#pragma unused 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);
}
}
}
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 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(entity.type[0] == '_') {
if(StrEqual(entity.type, "_gascan")) {
AddGascanSpawner(entity);
} else if(StrContains(entity.type, "_car") != -1) {
SpawnCar(entity);
} else {
Log("WARN: Unknown custom entity type \"%s\", skipped", entity.type);
}
} 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(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]);
R_CreateFire(entity);
} else if(StrEqual(entity.type, "light_dynamic")) {
R_CreateLight(entity);
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")) {
R_CreateEnvBlockerScaled(entity);
} 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);
R_CreateDecal(entity);
} else if(StrContains(entity.type, "weapon_") == 0 || 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;
}
R_CreateProp(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("Unsupported entity type \"%s\"", entity.type);
}
}
int CreateRope(VariantEntityData data) {
char targetName[32], nextKey[32];
Format(targetName, sizeof(targetName), "randomizer_rope%d", g_ropeIndex);
Format(nextKey, sizeof(nextKey), "randomizer_rope%d_0", g_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", g_ropeIndex, i);
if(i < data.keyframes.Length - 1) {
Format(nextKey, sizeof(nextKey), "randomizer_rope%d_%d", g_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", g_ropeIndex, data.keyframes.Length, entity);
g_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 DebugBox(const float origin[3], const float scale[3], int color[4]) {
// 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);
// }
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(bool clearEntities = true) {
if(g_MapData.scenes != null) {
g_MapData.Cleanup();
}
delete g_MapData.lumpEdits;
delete g_MapData.gascanSpawners;
// Cleanup all alarm car entities:
if(clearEntities) {
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);
}
}
DeleteCustomEnts();
}
// TODO: delete car alarms
g_gascanSpawners.Clear();
delete g_gascanRespawnQueue;
}