mirror of
https://github.com/Jackzmc/sourcemod-plugins.git
synced 2025-05-06 03:03:21 +00:00
599 lines
No EOL
16 KiB
SourcePawn
599 lines
No EOL
16 KiB
SourcePawn
#define MAX_SCENE_NAME_LENGTH 32
|
|
#define MAX_INPUTS_CLASSNAME_LENGTH 64
|
|
|
|
int DEFAULT_COLOR[4] = { 255, 255, 255, 255 };
|
|
|
|
MapData g_MapData; // The global map data
|
|
SceneSelection g_selection; // The selected scenes from the global map data
|
|
BuilderData g_builder; // The global instance of the builder
|
|
ArrayList g_mapTraverseSelectionStack; // For maps that traverse backwards, holds record of the selected scenes so they can be re-applied
|
|
|
|
int g_ropeIndex; // Unique id for ropes on spawn, is reset to 0 for every new spawn attempt
|
|
ArrayList g_gascanRespawnQueue; // Queue that gascan respawns pop from to respawn to
|
|
AnyMap g_gascanSpawners; // Mapping of <entity index, GascanSpawnerData>, for when a can is destroyed it can be respawned in position
|
|
|
|
int g_iLaserIndex;
|
|
|
|
public void InitGlobals() {
|
|
g_gascanSpawners = new AnyMap();
|
|
g_mapTraverseSelectionStack = new ArrayList(sizeof(TraverseData));
|
|
}
|
|
|
|
enum struct TraverseData {
|
|
char map[64];
|
|
ArrayList selection;
|
|
}
|
|
|
|
methodmap SceneSelection < ArrayList {
|
|
public SceneSelection() {
|
|
ArrayList selectedScenes = new ArrayList(sizeof(SelectedSceneData));
|
|
return view_as<SceneSelection>(selectedScenes);
|
|
}
|
|
|
|
property int Count {
|
|
public get() {
|
|
return (view_as<ArrayList>(this)).Length;
|
|
}
|
|
}
|
|
|
|
public void Cleanup() {
|
|
delete this;
|
|
}
|
|
|
|
public void Activate(MapData data, int flags = 0) {
|
|
g_ropeIndex = 0;
|
|
SelectedSceneData aScene;
|
|
SceneData scene;
|
|
SceneVariantData choice;
|
|
ArrayList list = view_as<ArrayList>(this);
|
|
for(int i = 0; i < list.Length; i++) {
|
|
list.GetArray(i, aScene);
|
|
Log("Activating scene \"%s\" with %d variants", aScene.name, aScene.selectedVariantIndexes.Length);
|
|
|
|
// Fetch the scene that aScene marks
|
|
if(!data.scenesKv.GetArray(aScene.name, scene, sizeof(scene))) {
|
|
Log("WARN: Selected scene \"%s\" not found, skipping", aScene.name);
|
|
continue;
|
|
}
|
|
|
|
for(int v = 0; v < aScene.selectedVariantIndexes.Length; v++) {
|
|
int variantIndex = aScene.selectedVariantIndexes.Get(v);
|
|
|
|
|
|
scene.variants.GetArray(variantIndex, choice);
|
|
activateVariant(choice, flags);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Get(int sceneIndex, SelectedSceneData scene) {
|
|
(view_as<ArrayList>(this)).GetArray(sceneIndex, scene);
|
|
}
|
|
|
|
property ArrayList List {
|
|
public get() {
|
|
return view_as<ArrayList>(this);
|
|
}
|
|
}
|
|
|
|
public void AddScene(SelectedSceneData aScene) {
|
|
view_as<ArrayList>(this).PushArray(aScene);
|
|
}
|
|
}
|
|
|
|
void StoreTraverseSelection(const char[] name, SceneSelection selection) {
|
|
// Pushes selection and map to the stack
|
|
TraverseData data;
|
|
strcopy(data.map, sizeof(data.map), name);
|
|
data.selection = selection.List.Clone();
|
|
g_mapTraverseSelectionStack.PushArray(data);
|
|
}
|
|
|
|
bool PopTraverseSelection(TraverseData data) {
|
|
if(g_mapTraverseSelectionStack.Length == 0) {
|
|
Log("WARN: PopTraverseSelection() called but stack is empty");
|
|
return false;
|
|
}
|
|
int index = g_mapTraverseSelectionStack.Length - 1;
|
|
g_mapTraverseSelectionStack.GetArray(index, data);
|
|
g_mapTraverseSelectionStack.Erase(index);
|
|
return true;
|
|
}
|
|
|
|
void ClearTraverseStack() {
|
|
TraverseData trav;
|
|
for(int i = 0; i < g_mapTraverseSelectionStack.Length; i++) {
|
|
g_mapTraverseSelectionStack.GetArray(i, trav, sizeof(trav));
|
|
delete trav.selection;
|
|
}
|
|
g_mapTraverseSelectionStack.Clear();
|
|
}
|
|
|
|
enum struct SelectedSceneData {
|
|
char name[MAX_SCENE_NAME_LENGTH];
|
|
ArrayList selectedVariantIndexes;
|
|
}
|
|
|
|
enum struct GascanSpawnerData {
|
|
float origin[3];
|
|
float angles[3];
|
|
}
|
|
|
|
enum struct MapData {
|
|
StringMap scenesKv;
|
|
ArrayList scenes;
|
|
ArrayList lumpEdits;
|
|
ArrayList activeScenes;
|
|
ArrayList gascanSpawners;
|
|
StringMap groups;
|
|
|
|
void Cleanup() {
|
|
SceneData scene;
|
|
for(int i = 0; i < this.scenes.Length; i++) {
|
|
this.scenes.GetArray(i, scene);
|
|
scene.Cleanup();
|
|
}
|
|
delete this.scenes;
|
|
delete this.scenesKv;
|
|
delete this.lumpEdits;
|
|
delete this.activeScenes;
|
|
delete this.gascanSpawners;
|
|
delete this.groups;
|
|
}
|
|
|
|
SceneSelection GenerateSelection(int flags) {
|
|
return selectScenes(this, flags);
|
|
}
|
|
|
|
bool ApplySelection(SceneSelection selection, int flags) {
|
|
Profiler profiler = new Profiler();
|
|
|
|
profiler.Start();
|
|
selection.Activate(this, flags);
|
|
spawnGascans(this);
|
|
profiler.Stop();
|
|
|
|
// _ropeIndex = 0;
|
|
|
|
Log("Done applying selection in %.4f seconds", profiler.Time);
|
|
return true;
|
|
}
|
|
|
|
bool IsLoaded() {
|
|
return this.scenes != null;
|
|
}
|
|
}
|
|
|
|
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
|
|
FLAG_IGNORE_TRAVERSE_STORE = 16 // Do not load stored selection from the g_mapTraverseSelections
|
|
}
|
|
|
|
enum struct BuilderData {
|
|
JSONObject mapData;
|
|
|
|
JSONObject selectedSceneData;
|
|
char selectedSceneId[64];
|
|
|
|
JSONObject selectedVariantData;
|
|
int selectedVariantIndex;
|
|
|
|
bool IsLoaded() {
|
|
return this.mapData != null;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
enum struct SceneData {
|
|
char name[MAX_SCENE_NAME_LENGTH];
|
|
float chance;
|
|
char group[MAX_SCENE_NAME_LENGTH];
|
|
ArrayList variants;
|
|
|
|
void Cleanup() {
|
|
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;
|
|
VariantEntityData entity;
|
|
for(int i = 0; i < this.entities.Length; i++) {
|
|
this.entities.GetArray(i, entity, sizeof(entity));
|
|
entity.Cleanup();
|
|
}
|
|
delete this.entities;
|
|
delete this.forcedScenes;
|
|
}
|
|
}
|
|
|
|
enum propertyType {
|
|
PROPERTY_NONE = -1,
|
|
PROPERTY_STRING,
|
|
PROPERTY_INTEGER,
|
|
PROPERTY_FLOAT
|
|
}
|
|
|
|
// This is horrible but we need a way to know what the type of the netprop to set is
|
|
enum struct PropertyStore {
|
|
JSONObject intKv;
|
|
JSONObject stringKv;
|
|
JSONObject floatKv;
|
|
|
|
void Cleanup() {
|
|
if(this.intKv != null) delete this.intKv;
|
|
if(this.stringKv != null) delete this.stringKv;
|
|
if(this.floatKv != null) delete this.floatKv;
|
|
}
|
|
|
|
bool GetInt(const char[] name, int &value) {
|
|
if(this.intKv == null) return false;
|
|
if(!this.intKv.HasKey(name)) return false;
|
|
value = this.intKv.GetInt(name);
|
|
return true;
|
|
}
|
|
|
|
bool GetString(const char[] name, char[] buffer, int maxlen) {
|
|
if(this.stringKv == null) return false;
|
|
if(!this.stringKv.HasKey(name)) return false;
|
|
this.stringKv.GetString(name, buffer, maxlen);
|
|
return true;
|
|
}
|
|
|
|
bool GetFloat(const char[] name, float &value) {
|
|
if(this.floatKv == null) return false;
|
|
if(!this.floatKv.HasKey(name)) return false;
|
|
value = this.floatKv.GetFloat(name);
|
|
return true;
|
|
}
|
|
|
|
propertyType GetPropertyType(const char[] key) {
|
|
if(this.intKv != null && this.intKv.HasKey(key)) return PROPERTY_INTEGER;
|
|
if(this.floatKv != null && this.floatKv.HasKey(key)) return PROPERTY_FLOAT;
|
|
if(this.stringKv != null && this.stringKv.HasKey(key)) return PROPERTY_STRING;
|
|
return PROPERTY_NONE;
|
|
}
|
|
|
|
bool HasAny() {
|
|
return this.intKv != null || this.floatKv != null || this.stringKv != null;
|
|
}
|
|
|
|
ArrayList Keys() {
|
|
char key[128];
|
|
ArrayList list = new ArrayList(ByteCountToCells(128));
|
|
JSONObjectKeys keys;
|
|
if(this.stringKv != null) {
|
|
keys = this.stringKv.Keys()
|
|
while(keys.ReadKey(key, sizeof(key))) {
|
|
list.PushString(key);
|
|
}
|
|
delete keys;
|
|
}
|
|
if(this.intKv != null) {
|
|
keys = this.intKv.Keys()
|
|
while(keys.ReadKey(key, sizeof(key))) {
|
|
list.PushString(key);
|
|
}
|
|
delete keys;
|
|
}
|
|
if(this.floatKv != null) {
|
|
keys = this.floatKv.Keys()
|
|
while(keys.ReadKey(key, sizeof(key))) {
|
|
list.PushString(key);
|
|
}
|
|
delete keys;
|
|
}
|
|
return list;
|
|
}
|
|
|
|
StringMap Entries() {
|
|
char key[128];
|
|
StringMap kv = new StringMap();
|
|
JSONObjectKeys keys;
|
|
if(this.stringKv != null) {
|
|
keys = this.stringKv.Keys()
|
|
while(keys.ReadKey(key, sizeof(key))) {
|
|
kv.SetValue(key, PROPERTY_STRING);
|
|
}
|
|
delete keys;
|
|
}
|
|
if(this.intKv != null) {
|
|
keys = this.intKv.Keys()
|
|
while(keys.ReadKey(key, sizeof(key))) {
|
|
kv.SetValue(key, PROPERTY_INTEGER);
|
|
}
|
|
delete keys;
|
|
}
|
|
if(this.floatKv != null) {
|
|
keys = this.floatKv.Keys()
|
|
while(keys.ReadKey(key, sizeof(key))) {
|
|
kv.SetValue(key, PROPERTY_FLOAT);
|
|
}
|
|
delete keys;
|
|
}
|
|
return kv;
|
|
}
|
|
}
|
|
|
|
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;
|
|
PropertyStore properties;
|
|
JSONObject propertiesInt;
|
|
JSONObject propertiesString;
|
|
JSONObject propertiesFloat;
|
|
|
|
void Cleanup() {
|
|
if(this.keyframes != null) {
|
|
delete this.keyframes;
|
|
}
|
|
this.properties.Cleanup();
|
|
}
|
|
|
|
void ApplyProperties(int entity) {
|
|
if(!this.properties.HasAny()) return;
|
|
char key[64], buffer[128];
|
|
ArrayList keys = this.properties.Keys();
|
|
for(int i = 0; i < keys.Length; i++) {
|
|
keys.GetString(i, key, sizeof(key));
|
|
// Only want to apply netprops (m_ prefix)
|
|
if(key[0] == 'm' && key[1] == '_') {
|
|
propertyType type = this.properties.GetPropertyType(key);
|
|
Debug("netprop %s type %d", key, type);
|
|
switch(type) {
|
|
case PROPERTY_STRING: {
|
|
this.properties.GetString(key, buffer, sizeof(buffer));
|
|
Debug("Applying netprop %s (val=%s) on %d", key, buffer, entity);
|
|
SetEntPropString(entity, Prop_Send, key, buffer);
|
|
break;
|
|
}
|
|
case PROPERTY_INTEGER: {
|
|
int val;
|
|
this.properties.GetInt(key, val);
|
|
Debug("Applying netprop %s (val=%d) on %d", key, val, entity);
|
|
SetEntProp(entity, Prop_Send, key, val);
|
|
break;
|
|
}
|
|
case PROPERTY_FLOAT: {
|
|
float val;
|
|
this.properties.GetFloat(key, val);
|
|
Debug("Applying netprop %s (val=%f) on %d", key, val, entity);
|
|
SetEntPropFloat(entity, Prop_Send, key, val);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
delete keys;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
} |