sourcemod-plugins/scripting/l4d2_randomizer.sp
2023-12-20 18:13:23 -06:00

781 lines
No EOL
23 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 <jutils>
#include <entitylump>
int g_iLaserIndex;
#if defined DEBUG_BLOCKERS
#include <smlib/effects>
#endif
#define ENT_PROP_NAME "l4d2_randomizer"
#define ENT_ENV_NAME "l4d2_randomizer"
#define ENT_BLOCKER_NAME "l4d2_randomizer"
#include <gamemodes/ents>
#define MAX_SCENE_NAME_LENGTH 32
#define MAX_INPUTS_CLASSNAME_LENGTH 64
public Plugin myinfo =
{
name = "L4D2 Randomizer",
author = "jackzmc",
description = "",
version = PLUGIN_VERSION,
url = "https://github.com/Jackzmc/sourcemod-plugins"
};
ConVar cvarEnabled;
enum struct ActiveSceneData {
char name[MAX_SCENE_NAME_LENGTH];
int variantIndex;
}
MapData g_MapData;
public void OnPluginStart() {
EngineVersion g_Game = GetEngineVersion();
if(g_Game != Engine_Left4Dead && g_Game != Engine_Left4Dead2) {
SetFailState("This plugin is for L4D/L4D2 only.");
}
RegAdminCmd("sm_rcycle", Command_CycleRandom, ADMFLAG_CHEATS);
RegAdminCmd("sm_expent", Command_ExportEnt, ADMFLAG_GENERIC);
cvarEnabled = CreateConVar("sm_randomizer_enabled", "0");
g_MapData.activeScenes = new ArrayList(sizeof(ActiveSceneData));
}
char currentMap[64];
// TODO: on round start
public void OnMapStart() {
g_iLaserIndex = PrecacheModel("materials/sprites/laserbeam.vmt", true);
GetCurrentMap(currentMap, sizeof(currentMap));
}
public void OnMapEnd() {
Cleanup();
}
bool hasRan;
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 = false;
}
public void OnClientPutInServer(int client) {
if(!hasRan) {
hasRan = true;
if(cvarEnabled.BoolValue)
RunMap(currentMap, FLAG_NONE);
}
}
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();
char arg1[8];
GetCmdArg(1, arg1, sizeof(arg1));
int flags = StringToInt(arg1) | view_as<int>(FLAG_REFRESH);
RunMap(currentMap, flags);
if(client > 0)
PrintCenterText(client, "Cycled flags=%d", flags);
} else {
ReplyToCommand(client, "Active Scenes:");
ActiveSceneData scene;
for(int i = 0; i < g_MapData.activeScenes.Length; i++) {
g_MapData.activeScenes.GetArray(i, scene);
ReplyToCommand(client, "\t%s: variant #%d", scene.name, scene.variantIndex);
}
}
return Plugin_Handled;
}
public Action Command_ExportEnt(int client, int args) {
float origin[3];
int entity = GetLookingPosition(client, Filter_IgnorePlayer, origin);
float angles[3];
float size[3];
char arg1[32];
GetCmdArg(1, arg1, sizeof(arg1));
if(entity > 0) {
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", origin);
GetEntPropVector(entity, Prop_Send, "m_angRotation", angles);
GetEntPropVector(entity, Prop_Send, "m_vecMaxs", size);
char model[64];
ReplyToCommand(client, "{");
GetEntityClassname(entity, model, sizeof(model));
if(StrContains(model, "prop_") == -1) {
ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]);
}
if(StrEqual(arg1, "hammerid")) {
int hammerid = GetEntProp(entity, Prop_Data, "m_iHammerID");
ReplyToCommand(client, "\t\"type\": \"hammerid\",");
ReplyToCommand(client, "\t\"model\": \"%d\",", hammerid);
} else if(StrEqual(arg1, "targetname")) {
GetEntPropString(entity, Prop_Data, "m_iName", model, sizeof(model));
ReplyToCommand(client, "\t\"type\": \"targetname\",");
ReplyToCommand(client, "\t\"model\": \"%s\",", model);
} else {
GetEntPropString(entity, Prop_Data, "m_ModelName", model, sizeof(model));
ReplyToCommand(client, "\t\"model\": \"%s\",", model);
}
ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]);
ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]);
ReplyToCommand(client, "}");
} else {
if(!StrEqual(arg1, "cursor"))
GetEntPropVector(client, Prop_Send, "m_vecOrigin", origin);
GetEntPropVector(client, Prop_Send, "m_angRotation", angles);
ReplyToCommand(client, "{");
ReplyToCommand(client, "\t\"type\": \"%s\",", arg1);
ReplyToCommand(client, "\t\"scale\": [%.2f, %.2f, %.2f],", size[0], size[1], size[2]);
ReplyToCommand(client, "\t\"origin\": [%.2f, %.2f, %.2f],", origin[0], origin[1], origin[2]);
ReplyToCommand(client, "\t\"angles\": [%.2f, %.2f, %.2f]", angles[0], angles[1], angles[2]);
ReplyToCommand(client, "}");
}
return Plugin_Handled;
}
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;
void Cleanup() {
delete this.inputsList;
delete this.entities;
}
}
enum struct VariantEntityData {
char type[32];
char model[64];
float origin[3];
float angles[3];
float scale[3];
int color[4];
}
enum InputType {
Input_Classname,
Input_Targetname,
Input_HammerId
}
enum struct VariantInputData {
char name[MAX_INPUTS_CLASSNAME_LENGTH];
InputType type;
char input[32];
void Trigger() {
int entity = -1;
switch(this.type) {
case Input_Classname: {
entity = FindEntityByClassname(entity, this.name);
this._trigger(entity);
}
case Input_Targetname: {
char targetname[32];
while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) {
GetEntPropString(entity, Prop_Data, "m_iName", targetname, sizeof(targetname));
if(StrEqual(targetname, this.name)) {
this._trigger(entity);
}
}
}
case Input_HammerId: {
int targetId = StringToInt(this.name);
while((entity = FindEntityByClassname(entity, "*")) != INVALID_ENT_REFERENCE) {
int hammerId = GetEntProp(entity, Prop_Data, "m_iHammerID");
if(hammerId == targetId ) {
this._trigger(entity);
break;
}
}
}
}
}
void _trigger(int entity) {
if(entity > 0 && IsValidEntity(entity)) {
if(StrEqual(this.input, "_allow_ladder")) {
if(HasEntProp(entity, Prop_Send, "m_iTeamNum")) {
SetEntProp(entity, Prop_Send, "m_iTeamNum", 0);
} else {
Log("Warn: Entity (%d) with id \"%s\" has no teamnum for \"_allow_ladder\"", entity, this.name);
}
} else if(StrEqual(this.input, "_lock")) {
AcceptEntityInput(entity, "Close");
AcceptEntityInput(entity, "Lock");
} else if(StrEqual(this.input, "_lock_nobreak")) {
AcceptEntityInput(entity, "Close");
AcceptEntityInput(entity, "Lock");
AcceptEntityInput(entity, "SetUnbreakable");
}else {
char cmd[32];
// Split input "a b" to a with variant "b"
int len = SplitString(this.input, " ", cmd, sizeof(cmd));
if(len > -1) SetVariantString(this.input[len]);
Debug("_trigger(%d): %s (v=%s)", entity, this.input, cmd);
AcceptEntityInput(entity, this.input);
}
}
}
}
enum struct LumpEditData {
char name[MAX_INPUTS_CLASSNAME_LENGTH];
InputType type;
char action[32];
char value[64];
int _findLumpIndex(int startIndex = 0, EntityLumpEntry entry) {
int length = EntityLump.Length();
char val[64];
Debug("Scanning for \"%s\" (type=%d)", this.name, this.type);
for(int i = startIndex; i < length; i++) {
entry = EntityLump.Get(i);
int index = entry.FindKey("hammerid");
if(index != -1) {
entry.Get(index, "", 0, val, sizeof(val));
if(StrEqual(val, this.name)) {
return i;
}
}
index = entry.FindKey("classname");
if(index != -1) {
entry.Get(index, "", 0, val, sizeof(val));
Debug("%s vs %s", val, this.name);
if(StrEqual(val, this.name)) {
return i;
}
}
index = entry.FindKey("targetname");
if(index != -1) {
entry.Get(index, "", 0, val, sizeof(val));
if(StrEqual(val, this.name)) {
return i;
}
}
delete entry;
}
Log("Warn: Could not find any matching lump for \"%s\" (type=%d)", this.name, this.type);
return -1;
}
void Trigger() {
int index = 0;
EntityLumpEntry entry;
while((index = this._findLumpIndex(index, entry) != -1)) {
// for(int i = 0; i < entry.Length; i++) {
// entry.Get(i, a, sizeof(a), v, sizeof(v));
// Debug("%s=%s", a, v);
// }
this._trigger(entry);
}
}
void _updateKey(EntityLumpEntry entry, const char[] key, const char[] value) {
int index = entry.FindKey(key);
if(index != -1) {
Debug("update key %s = %s", key, value);
entry.Update(index, key, value);
}
}
void _trigger(EntityLumpEntry entry) {
if(StrEqual(this.action, "setclassname")) {
this._updateKey(entry, "classname", this.value);
}
delete entry;
}
}
enum struct MapData {
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
}
// Reads (mapname).json file and parses it
public bool LoadMapData(const char[] map, int flags) {
Debug("Loading config for %s", map);
char filePath[PLATFORM_MAX_PATH];
BuildPath(Path_SM, filePath, sizeof(filePath), "data/randomizer/%s.json", map);
if(!FileExists(filePath)) {
Log("[Randomizer] No map config file (data/randomizer/%s.json), not loading", map);
return false;
}
char buffer[65536];
File file = OpenFile(filePath, "r");
if(file == null) {
LogError("Could not open map config file (data/randomizer/%s.json)", map);
return false;
}
file.ReadString(buffer, sizeof(buffer));
JSON_Object data = json_decode(buffer);
if(data == null) {
json_get_last_error(buffer, sizeof(buffer));
LogError("Could not parse map config file (data/randomizer/%s.json): %s", map, buffer);
delete file;
return false;
}
Debug("Starting parsing json data");
Cleanup();
g_MapData.scenes = new ArrayList(sizeof(SceneData));
g_MapData.lumpEdits = new ArrayList(sizeof(LumpEditData));
g_MapData.activeScenes.Clear();
Profiler profiler = new Profiler();
profiler.Start();
int length = data.Length;
char key[32];
for (int i = 0; i < length; i += 1) {
data.GetKey(i, key, sizeof(key));
if(key[0] == '_') {
if(StrEqual(key, "_lumps")) {
JSON_Array lumpsList = view_as<JSON_Array>(data.GetObject(key));
if(lumpsList != null) {
for(int l = 0; l < lumpsList.Length; l++) {
loadLumpData(g_MapData.lumpEdits, lumpsList.GetObject(l));
}
}
} else {
Debug("Unknown special entry \"%s\", skipping", key);
}
} else {
if(data.GetType(key) != JSON_Type_Object) {
Debug("Invalid normal entry \"%s\" (not an object), skipping", key);
continue;
}
JSON_Object scene = data.GetObject(key);
// Parses scene data and inserts to scenes
loadScene(key, scene);
}
}
json_cleanup_and_delete(data);
profiler.Stop();
Log("Parsed map file and found %d scenes in %.4f seconds", g_MapData.scenes.Length, profiler.Time);
delete profiler;
delete file;
return true;
}
// Calls LoadMapData (read&parse (mapname).json) then select scenes
public bool RunMap(const char[] map, int flags) {
if(g_MapData.scenes == null || flags & view_as<int>(FLAG_REFRESH)) {
LoadMapData(map, flags);
}
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], JSON_Object 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;
}
sceneData.GetString("group", scene.group, sizeof(scene.group));
scene.variants = new ArrayList(sizeof(SceneVariantData));
JSON_Array variants = view_as<JSON_Array>(sceneData.GetObject("variants"));
for(int i = 0; i < variants.Length; i++) {
// Parses choice and loads to scene.choices
loadChoice(scene, variants.GetObject(i));
}
g_MapData.scenes.PushArray(scene);
}
void loadChoice(SceneData scene, JSON_Object choiceData) {
SceneVariantData choice;
choice.weight = choiceData.GetInt("weight", 1);
choice.entities = new ArrayList(sizeof(VariantEntityData));
choice.inputsList = new ArrayList(sizeof(VariantInputData));
JSON_Array entities = view_as<JSON_Array>(choiceData.GetObject("entities"));
if(entities != null) {
for(int i = 0; i < entities.Length; i++) {
// Parses entities and loads to choice.entities
loadChoiceEntity(choice.entities, entities.GetObject(i));
}
}
JSON_Array inputsList = view_as<JSON_Array>(choiceData.GetObject("inputs"));
if(inputsList != null) {
for(int i = 0; i < inputsList.Length; i++) {
loadChoiceInput(choice.inputsList, inputsList.GetObject(i));
}
}
scene.variants.PushArray(choice);
}
void loadChoiceInput(ArrayList list, JSON_Object inputData) {
VariantInputData input;
// Check classname -> targetname -> hammerid
if(!inputData.GetString("classname", input.name, sizeof(input.name))) {
if(inputData.GetString("targetname", input.name, sizeof(input.name))) {
input.type = Input_Targetname;
} else {
if(inputData.GetString("hammerid", input.name, sizeof(input.name))) {
input.type = Input_HammerId;
} else {
int id = inputData.GetInt("hammerid");
if(id > 0) {
input.type = Input_HammerId;
IntToString(id, input.name, sizeof(input.name));
} else {
LogError("Missing valid input specification (hammerid, classname, targetname)");
return;
}
}
}
}
inputData.GetString("input", input.input, sizeof(input.input));
list.PushArray(input);
}
void loadLumpData(ArrayList list, JSON_Object 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, JSON_Object 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);
}
void GetVector(JSON_Object obj, const char[] key, float out[3]) {
JSON_Array vecArray = view_as<JSON_Array>(obj.GetObject(key));
if(vecArray != null) {
out[0] = vecArray.GetFloat(0);
out[1] = vecArray.GetFloat(1);
out[2] = vecArray.GetFloat(2);
}
}
void GetColor(JSON_Object obj, const char[] key, int out[4]) {
JSON_Array vecArray = view_as<JSON_Array>(obj.GetObject(key));
if(vecArray != null) {
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[0] = 255;
out[1] = 255;
out[2] = 255;
out[3] = 255;
}
}
void selectScenes(int flags = 0) {
SceneData scene;
StringMap groups = new StringMap();
ArrayList list;
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 {
if(!groups.GetValue(scene.group, list)) {
list = new ArrayList();
}
list.Push(i);
groups.SetValue(scene.group, list);
}
}
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);
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;
}
delete snapshot;
delete groups;
}
void selectScene(SceneData scene, int flags) {
// Use the .chance field unless FLAG_ALL_SCENES is set
if(~flags & view_as<int>(FLAG_ALL_SCENES) && GetURandomFloat() > scene.chance) {
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;
// 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 {
for(int c = 0; c < choice.weight; c++) {
choices.Push(i);
}
}
}
if(flags & view_as<int>(FLAG_ALL_VARIANTS)) {
delete choices;
} else {
index = GetURandomInt() % choices.Length;
index = choices.Get(index);
delete choices;
Log("Spawned scene \"%s\" with variant #%d", scene.name, index);
scene.variants.GetArray(index, choice);
spawnVariant(choice);
}
ActiveSceneData aScene;
strcopy(aScene.name, sizeof(aScene.name), scene.name);
aScene.variantIndex = index;
g_MapData.activeScenes.PushArray(aScene);
}
void spawnVariant(SceneVariantData choice) {
VariantEntityData entity;
for(int i = 0; i < choice.entities.Length; i++) {
choice.entities.GetArray(i, entity);
spawnEntity(entity);
}
if(choice.inputsList.Length > 0) {
VariantInputData input;
for(int i = 0; i < choice.inputsList.Length; i++) {
choice.inputsList.GetArray(i, input);
input.Trigger();
}
}
}
void spawnEntity(VariantEntityData entity) {
if(StrEqual(entity.type, "env_fire")) {
Debug("spawning \"%s\" at (%.1f %.1f %.1f) rot (%.0f %.0f %.0f)", entity.type, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]);
CreateFire(entity.origin, 20.0, 100.0, 0.0);
} else if(StrEqual(entity.type, "env_physics_blocker") || StrEqual(entity.type, "env_player_blocker")) {
CreateEnvBlockerScaled(entity.type, entity.origin, entity.scale);
} else if(StrEqual(entity.type, "infodecal")) {
CreateDecal(entity.model, entity.origin);
} else if(StrContains(entity.type, "prop_") == 0) {
if(entity.model[0] == '\0') {
LogError("Missing model for entity with type \"%s\"", entity.type);
return;
}
PrecacheModel(entity.model);
int prop = CreateProp(entity.type, entity.model, entity.origin, entity.angles);
SetEntityRenderColor(prop, entity.color[0], entity.color[1], entity.color[2], entity.color[3]);
} else if(StrEqual(entity.type, "hammerid")) {
int targetId = StringToInt(entity.model);
if(targetId > 0) {
int ent = -1;
while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) {
int hammerId = GetEntProp(ent, Prop_Data, "m_iHammerID");
if(hammerId == targetId) {
Debug("moved entity (hammerid=%d) to %.0f %.0f %.0f rot %.0f %.0f %.0f", targetId, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]);
TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR);
return;
}
}
}
Debug("Warn: Could not find entity (hammerid=%d) (model=%s)", targetId, entity.model);
} else if(StrEqual(entity.type, "targetname")) {
int ent = -1;
char targetname[64];
bool found = false;
while((ent = FindEntityByClassname(ent, "*")) != INVALID_ENT_REFERENCE) {
GetEntPropString(ent, Prop_Data, "m_iName", targetname, sizeof(targetname));
if(StrEqual(entity.model, targetname)) {
Debug("moved entity (targetname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]);
TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR);
found = true;
}
}
if(!found)
Debug("Warn: Could not find entity (targetname=%s)", entity.model);
} else if(StrEqual(entity.type, "classname")) {
int ent = -1;
char classname[64];
bool found;
while((ent = FindEntityByClassname(ent, classname)) != INVALID_ENT_REFERENCE) {
Debug("moved entity (classname=%s) to %.0f %.0f %.0f rot %.0f %.0f %.0f", entity.model, entity.origin[0], entity.origin[1], entity.origin[2], entity.angles[0], entity.angles[1], entity.angles[2]);
TeleportEntity(ent, entity.origin, entity.angles, NULL_VECTOR);
found = true;
}
if(!found)
Debug("Warn: Could not find entity (classname=%s)", entity.model);
} else {
LogError("Unknown entity type \"%s\"", entity.type);
}
}
void Debug(const char[] format, any ...) {
#if defined DEBUG_SCENE_PARSE
char buffer[192];
VFormat(buffer, sizeof(buffer), format, 2);
PrintToServer("[Randomizer::Debug] %s", buffer);
PrintToConsoleAll("[Randomizer::Debug] %s", buffer);
#endif
}
void Log(const char[] format, any ...) {
char buffer[192];
VFormat(buffer, sizeof(buffer), format, 2);
PrintToServer("[Randomizer] %s", buffer);
}
void Cleanup() {
if(g_MapData.scenes != null) {
SceneData scene;
for(int i = 0; i < g_MapData.scenes.Length; i++) {
g_MapData.scenes.GetArray(i, scene);
scene.Cleanup();
}
delete g_MapData.scenes;
}
delete g_MapData.lumpEdits;
DeleteCustomEnts();
g_MapData.activeScenes.Clear();
}