Last active
August 31, 2022 15:31
-
-
Save ronyx69/280e5695b3caeac7ef8b356cc36e0a7c to your computer and use it in GitHub Desktop.
Source code for Realtime mod. Ingame texture and mesh replacement. UI by Simon Royer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using UnityEngine; | |
using System.IO; | |
using ColossalFramework.IO; | |
using ObjUnity3D; | |
using ICities; | |
namespace Realtime | |
{ | |
public class Realtime_Mod : LoadingExtensionBase, IUserMod | |
{ | |
public static GameObject gameObject; | |
public override void OnLevelLoaded(LoadMode mode) | |
{ | |
base.OnLevelLoaded(mode); | |
if (mode != LoadMode.NewGame && mode != LoadMode.LoadGame) | |
{ | |
return; | |
} | |
if (gameObject != null) | |
{ | |
return; | |
} | |
gameObject = new GameObject("Realtime_Mod"); | |
gameObject.AddComponent<RealtimeUI>(); | |
} | |
public override void OnLevelUnloading() | |
{ | |
base.OnLevelUnloading(); | |
if (gameObject == null) | |
{ | |
return; | |
} | |
UnityEngine.Object.Destroy(gameObject); | |
gameObject = null; | |
} | |
public string Name | |
{ | |
get { return "Realtime"; } | |
} | |
public string Description | |
{ | |
get { return "Realtime"; } | |
} | |
} | |
public class RealtimeUI : MonoBehaviour | |
{ | |
// Global variables | |
public bool showWindow; | |
private Rect windowRect; | |
private bool uiMode = true; // true = main | false = asset specified | |
// Search variables | |
private string searchToolbar = string.Empty; | |
private List<PropInfo> searchedProps; | |
private List<VehicleInfo> searchedVehicles; | |
private List<TreeInfo> searchedTrees; | |
private List<BuildingInfo> searchedBuildings; | |
private Vector2 searchScrollPos = Vector2.zero; | |
// Selected Asset variables | |
public string assetType; | |
public PropInfo selectedProp; | |
public VehicleInfo selectedVehicle; | |
public TreeInfo selectedTree; | |
public BuildingInfo selectedBuilding; | |
// Replacement UI variables | |
private string FilesLocation; | |
private string filesPrefix = string.Empty; | |
private string[] texSelectionStrings = new string[] { "Normal", "LOD" }; | |
// Sub Buildings variables | |
private bool scrollingThroughSubBuildings = false; | |
private Vector2 scrollSubPosition = Vector2.zero; | |
// MonoBehaviour functions | |
void Start() | |
{ | |
FilesLocation = DataLocation.addonsPath + "\\Import\\"; | |
windowRect = new Rect(150, 150, 500, 600); | |
searchedProps = new List<PropInfo>(); | |
searchedVehicles = new List<VehicleInfo>(); | |
searchedTrees = new List<TreeInfo>(); | |
searchedBuildings = new List<BuildingInfo>(); | |
} | |
void OnGUI() | |
{ | |
if (showWindow) | |
windowRect = GUI.Window(this.GetInstanceID(), windowRect, DrawWindow, "Realtime Replacement"); | |
} | |
void Update() | |
{ | |
if (Input.GetKey(KeyCode.LeftShift)) | |
{ | |
if (Input.GetKey(KeyCode.LeftAlt)) | |
{ | |
if (Input.GetKeyDown(KeyCode.R)) | |
{ | |
showWindow = !showWindow; | |
} | |
} | |
} | |
} | |
// Window UI function | |
void DrawWindow(int winID) | |
{ | |
GUI.DragWindow(new Rect(0, 0, 460, 30)); | |
if (RealtimeUtils.IsDroppableToolEnabled() && windowRect.Contains(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y))) | |
{ | |
if (GUI.Button(new Rect(10, 40, 480, 550), "<size=42>▼</size>\n<size=70>Drop</size>\n<size=42>asset\nhere</size>")) | |
{ | |
if (TryDropPrefab()) | |
{ | |
RealtimeUtils.ResetTool(); | |
uiMode = false; | |
} | |
} | |
} | |
else | |
{ | |
if (uiMode) | |
{ | |
if (GUI.Button(new Rect(465, 3, 30, 28), "X")) | |
showWindow = false; | |
GUI.Label(new Rect(10, 40, 480, 30), "Search for an asset to edit, or drop it on the window."); | |
searchToolbar = GUI.TextField(new Rect(10, 68, 400, 28), searchToolbar); | |
if (GUI.Button(new Rect(415, 68, 75, 28), "Search")) | |
SearchPrefabs(searchToolbar); | |
GUI.Box(new Rect(10, 100, 480, 490), string.Empty); | |
if ((searchedProps.Count + searchedVehicles.Count + searchedTrees.Count + searchedBuildings.Count) == 0) | |
GUI.Label(new Rect(20, 110, 460, 30), "No asset match this string !"); | |
else | |
{ | |
searchScrollPos = GUI.BeginScrollView(new Rect(15, 105, 470, 480), searchScrollPos, new Rect(0, 0, 450, 43 * searchedProps.Count + 43 * searchedVehicles.Count + 43 * searchedBuildings.Count + 43 * searchedTrees.Count)); | |
int yPos = 0; | |
for (int i = 0; i < searchedProps.Count; i++) | |
{ | |
GUI.Box(new Rect(2, yPos + 2, 448, 40), string.Empty); | |
GUI.color = Color.cyan; | |
GUI.Label(new Rect(10, yPos + 8, 55, 30), "[Prop]"); | |
GUI.color = Color.white; | |
GUI.Label(new Rect(65, yPos + 5, 280, 38), searchedProps[i].GetLocalizedTitle().Replace("PROPS_TITLE[", "").Replace("]:0", "") + "\n(<i>" + searchedProps[i].name + "</i>)"); | |
if (GUI.Button(new Rect(350, yPos + 9, 100, 27), "More ►")) | |
SelectPrefab<PropInfo>(searchedProps[i]); | |
yPos += 43; | |
} | |
for (int i = 0; i < searchedBuildings.Count; i++) | |
{ | |
GUI.Box(new Rect(2, yPos + 2, 448, 40), string.Empty); | |
GUI.color = Color.red; | |
GUI.Label(new Rect(10, yPos + 8, 55, 30), "[Building]"); | |
GUI.color = Color.white; | |
GUI.Label(new Rect(65, yPos + 5, 280, 38), searchedBuildings[i].GetLocalizedTitle().Replace("BUILDING_TITLE[", "").Replace("]:0", "") + "\n(<i>" + searchedBuildings[i].name + "</i>)"); | |
if (GUI.Button(new Rect(350, yPos + 9, 100, 27), "More ►")) | |
SelectPrefab<BuildingInfo>(searchedBuildings[i]); | |
yPos += 43; | |
} | |
for (int i = 0; i < searchedVehicles.Count; i++) | |
{ | |
GUI.Box(new Rect(2, yPos + 2, 448, 40), string.Empty); | |
GUI.color = Color.yellow; | |
GUI.Label(new Rect(10, yPos + 8, 55, 30), "[Vehicle]"); | |
GUI.color = Color.white; | |
GUI.Label(new Rect(65, yPos + 5, 280, 38), searchedVehicles[i].GetLocalizedTitle() + "\n(<i>" + searchedVehicles[i].name + "</i>)"); | |
if (GUI.Button(new Rect(350, yPos + 9, 100, 27), "More ►")) | |
SelectPrefab<VehicleInfo>(searchedVehicles[i]); | |
yPos += 43; | |
} | |
for (int i = 0; i < searchedTrees.Count; i++) | |
{ | |
GUI.Box(new Rect(2, yPos + 2, 448, 40), string.Empty); | |
GUI.color = Color.green; | |
GUI.Label(new Rect(10, yPos + 8, 55, 30), "[Tree]"); | |
GUI.color = Color.white; | |
GUI.Label(new Rect(65, yPos + 5, 280, 38), searchedTrees[i].GetLocalizedTitle() + "\n(<i>" + searchedTrees[i].name + "</i>)"); | |
if (GUI.Button(new Rect(350, yPos + 9, 100, 27), "More ►")) | |
SelectPrefab<TreeInfo>(searchedTrees[i]); | |
yPos += 43; | |
} | |
GUI.EndScrollView(); | |
} | |
} | |
else // => if uiMode == false | |
{ | |
if (scrollingThroughSubBuildings) | |
{ | |
if (GUI.Button(new Rect(465, 3, 30, 28), "←")) | |
{ | |
scrollingThroughSubBuildings = false; | |
scrollSubPosition = Vector2.zero; | |
} | |
GUI.Label(new Rect(10, 35, 480, 35), "<size=22>" + selectedBuilding.GetLocalizedTitle().RemoveMissingTranslationString().PossessionApostrophe() + " Sub-buildings" + "</size>"); | |
GUI.Label(new Rect(10, 68, 410, 28), "Choose a sub building, or click 'Back'"); | |
if (GUI.Button(new Rect(430, 68, 60, 28), "Back")) | |
{ | |
scrollingThroughSubBuildings = false; | |
scrollSubPosition = Vector2.zero; | |
} | |
GUI.Box(new Rect(10, 100, 480, 490), string.Empty); | |
scrollSubPosition = GUI.BeginScrollView(new Rect(15, 105, 470, 480), scrollSubPosition, new Rect(0, 0, 450, selectedBuilding.m_subBuildings.Count() * 43)); | |
int yPos = 0; | |
for (int i = 0; i < selectedBuilding.m_subBuildings.Count(); i++) | |
{ | |
GUI.Box(new Rect(2, yPos + 2, 448, 40), string.Empty); | |
GUI.color = Color.red; | |
GUI.Label(new Rect(10, yPos + 8, 55, 30), "[Building]"); | |
GUI.color = Color.white; | |
GUI.Label(new Rect(65, yPos + 5, 280, 38), selectedBuilding.m_subBuildings[i].m_buildingInfo.GetLocalizedTitle().Replace("BUILDING_TITLE[", "").Replace("]:0", "") + "\n(<i>" + selectedBuilding.m_subBuildings[i].m_buildingInfo.name + "</i>)"); | |
if (GUI.Button(new Rect(350, yPos + 9, 100, 27), "More ►")) | |
{ | |
scrollingThroughSubBuildings = false; | |
scrollSubPosition = Vector2.zero; | |
SelectPrefab<BuildingInfo>(selectedBuilding.m_subBuildings[i].m_buildingInfo); | |
} | |
yPos += 43; | |
} | |
GUI.EndScrollView(); | |
} | |
else | |
{ | |
if (GUI.Button(new Rect(465, 3, 30, 28), "←")) | |
ResetSelection(); | |
switch (assetType) | |
{ | |
case "prop": | |
GUI.color = Color.cyan; | |
GUI.Label(new Rect(10, 40, 55, 30), "[Prop]"); | |
break; | |
case "building": | |
GUI.color = Color.red; | |
GUI.Label(new Rect(10, 40, 55, 30), "[Building]"); | |
break; | |
case "tree": | |
GUI.color = Color.green; | |
GUI.Label(new Rect(10, 40, 55, 30), "[Tree]"); | |
break; | |
case "vehicle": | |
GUI.color = Color.yellow; | |
GUI.Label(new Rect(10, 38, 55, 30), "[Vehicle]"); | |
break; | |
} | |
GUI.color = Color.white; | |
GUI.Label(new Rect(10, 60, 480, 35), "<size=25>" + this.SelectedAsPrefabInfo().GetLocalizedTitle().RemoveMissingTranslationString() + "</size>"); | |
GUI.Label(new Rect(10, 88, 480, 30), "Asset name : <i>" + this.SelectedAsPrefabInfo().name + "</i>"); | |
// Mesh replacement UI | |
GUI.Box(new Rect(10, 118, 480, 35), string.Empty); | |
// Mesh Text field | |
GUI.Label(new Rect(16, 126, 130, 25), "Files Prefix"); | |
filesPrefix = GUI.TextField(new Rect(130, 122, 350, 26), filesPrefix); | |
GUI.Box(new Rect(10, 160, 480, 290), string.Empty); | |
// Mesh Replacement | |
GUI.Label(new Rect(190, 165, 480, 28), "<b>Meshes Replacement</b>"); | |
RealtimeUtils.UIReplace<string, string>(new Vector2(20, 195), "Mesh file", "Mesh", filesPrefix, ".obj", FilesLocation, ReplaceMesh, assetType, filesPrefix); | |
// Textures replacement UI | |
GUI.Label(new Rect(180, 230, 480, 28), "<b>Textures Replacement</b>"); | |
RealtimeUtils.UIReplace<string, string, TexturesReplacement>(new Vector2(20, 260), "Diffuse", "Diffuse", filesPrefix, "_d.png", FilesLocation, CallReplaceTextures, assetType, filesPrefix, TexturesReplacement.Diffuse); | |
RealtimeUtils.UIReplace<string, string, TexturesReplacement>(new Vector2(20, 290), "Alpha", "Alpha", filesPrefix, "_a.png", FilesLocation, CallReplaceTextures, assetType, filesPrefix, TexturesReplacement.Alpha); | |
RealtimeUtils.UIReplace<string, string, TexturesReplacement>(new Vector2(20, 320), "Normal", "Normal", filesPrefix, "_n.png", FilesLocation, CallReplaceTextures, assetType, filesPrefix, TexturesReplacement.Normal); | |
RealtimeUtils.UIReplace<string, string, TexturesReplacement>(new Vector2(20, 410), "Color", "Color", filesPrefix, "_c.png", FilesLocation, CallReplaceTextures, assetType, filesPrefix, TexturesReplacement.Color); | |
if (assetType == "tree") | |
{ | |
RealtimeUtils.UIReplaceUnavailable(new Vector2(20, 380), "Illumination", "trees"); | |
RealtimeUtils.UIReplaceUnavailable(new Vector2(20, 350), "Specular", "trees"); | |
} | |
else | |
{ | |
RealtimeUtils.UIReplace<string, string, TexturesReplacement>(new Vector2(20, 380), "Illumination", "Illum.", filesPrefix, "_i.png", FilesLocation, CallReplaceTextures, assetType, filesPrefix, TexturesReplacement.Illumination); | |
RealtimeUtils.UIReplace<string, string, TexturesReplacement>(new Vector2(20, 350), "Specular", "Specular", filesPrefix, "_s.png", FilesLocation, CallReplaceTextures, assetType, filesPrefix, TexturesReplacement.Specular); | |
} | |
if (RealtimeUtils.AnyFileExists(FilesLocation, filesPrefix, RealtimeUtils.AllFilesExtensions(assetType))) | |
{ | |
if (GUI.Button(new Rect(10, 460, 480, 60), "Replace every existing file")) | |
{ | |
if (assetType == "building") | |
ReplaceEverything<BuildingInfo>(selectedBuilding, filesPrefix); | |
else if (assetType == "prop") | |
ReplaceEverything<PropInfo>(selectedProp, filesPrefix); | |
else if (assetType == "tree") | |
ReplaceEverything<TreeInfo>(selectedTree, filesPrefix); | |
else if (assetType == "vehicle") | |
ReplaceEverything<VehicleInfo>(selectedVehicle, filesPrefix); | |
} | |
} | |
else | |
{ | |
GUI.color = Color.red; | |
GUI.Label(new Rect(10, 460, 480, 60), "[NO FILE FOUND]", GUI.skin.button); | |
GUI.color = Color.white; | |
} | |
if (GUI.Button(new Rect(10, 525, 480, 28), "Open Files Folder")) | |
Application.OpenURL("file://" + FilesLocation); | |
if (assetType == "building") | |
{ | |
if (selectedBuilding.m_subBuildings.Count() > 0) | |
{ | |
if (GUI.Button(new Rect(10, 560, 237, 28), "Sub-Buildings")) | |
scrollingThroughSubBuildings = true; | |
if (GUI.Button(new Rect(253, 560, 237, 28), "← Back")) | |
ResetSelection(); | |
} | |
else | |
{ | |
if (GUI.Button(new Rect(10, 560, 480, 28), "← Back")) | |
ResetSelection(); | |
} | |
} | |
else | |
{ | |
if (GUI.Button(new Rect(10, 555, 480, 28), "← Back")) | |
ResetSelection(); | |
} | |
} | |
} | |
} | |
} | |
// Private methods | |
private void SearchPrefabs(string searchText) | |
{ | |
if (searchText == string.Empty || searchText == "") | |
return; | |
string criteria = searchText.ToLower().Replace(" ", ""); | |
searchedProps = new List<PropInfo>(); | |
searchedVehicles = new List<VehicleInfo>(); | |
searchedTrees = new List<TreeInfo>(); | |
searchedBuildings = new List<BuildingInfo>(); | |
foreach (PropInfo prop in Resources.FindObjectsOfTypeAll<PropInfo>() | |
.Where(p => p.GetLocalizedTitle().ToLower().Replace("PROPS_TITLE[", "").Replace("]:0", "").Replace(" ", "").Contains(criteria) || p.name.ToLower().Replace(" ", "").Contains(criteria))) | |
{ | |
searchedProps.Add(prop); | |
} | |
foreach (BuildingInfo building in Resources.FindObjectsOfTypeAll<BuildingInfo>() | |
.Where(p => p.GetLocalizedTitle().ToLower().Replace("BUILDING_TITLE[", "").Replace("]:0", "").Replace(" ", "").Contains(criteria) || p.name.ToLower().Replace(" ", "").Contains(criteria))) | |
{ | |
searchedBuildings.Add(building); | |
} | |
foreach (TreeInfo tree in Resources.FindObjectsOfTypeAll<TreeInfo>() | |
.Where(p => p.GetLocalizedTitle().ToLower().Replace(" ", "").Contains(criteria) || p.name.ToLower().Replace(" ", "").Contains(criteria))) | |
{ | |
searchedTrees.Add(tree); | |
} | |
foreach (VehicleInfo vehicle in Resources.FindObjectsOfTypeAll<VehicleInfo>() | |
.Where(p => p.GetLocalizedTitle().ToLower().Replace(" ", "").Contains(criteria) || p.name.ToLower().Replace(" ", "").Contains(criteria))) | |
{ | |
searchedVehicles.Add(vehicle); | |
} | |
} | |
private bool TryDropPrefab() | |
{ | |
switch (ToolsModifierControl.toolController.CurrentTool.GetType().ToString()) | |
{ | |
case "PropTool": | |
assetType = "prop"; | |
selectedProp = (ToolsModifierControl.toolController.CurrentTool as PropTool).m_prefab; | |
filesPrefix = selectedProp.name.ExtractFilename(); | |
return true; | |
case "BuildingTool": | |
assetType = "building"; | |
selectedBuilding = (ToolsModifierControl.toolController.CurrentTool as BuildingTool).m_prefab; | |
filesPrefix = selectedBuilding.name.ExtractFilename(); | |
return true; | |
case "TreeTool": | |
assetType = "tree"; | |
selectedTree = (ToolsModifierControl.toolController.CurrentTool as TreeTool).m_prefab; | |
filesPrefix = selectedTree.name.ExtractFilename(); | |
return true; | |
default: | |
return false; | |
} | |
} | |
private void ResetSelection() | |
{ | |
uiMode = true; | |
assetType = string.Empty; | |
selectedProp = null; | |
selectedTree = null; | |
selectedBuilding = null; | |
selectedVehicle = null; | |
} | |
private void SelectPrefab<T>(T prefabInfo) where T : PrefabInfo | |
{ | |
if (prefabInfo.GetType() == typeof(PropInfo)) | |
{ | |
assetType = "prop"; | |
selectedProp = prefabInfo as PropInfo; | |
} | |
else if (prefabInfo.GetType() == typeof(BuildingInfo)) | |
{ | |
assetType = "building"; | |
selectedBuilding = prefabInfo as BuildingInfo; | |
} | |
else if (prefabInfo.GetType() == typeof(TreeInfo)) | |
{ | |
assetType = "tree"; | |
selectedTree = prefabInfo as TreeInfo; | |
} | |
else if (prefabInfo.GetType() == typeof(VehicleInfo)) | |
{ | |
assetType = "vehicle"; | |
selectedVehicle = prefabInfo as VehicleInfo; | |
} | |
filesPrefix = this.SelectedAsPrefabInfo().name.ExtractFilename(); | |
uiMode = false; | |
} | |
private void ReplaceEverything<T>(T prefab, string fileName) where T : PrefabInfo | |
{ | |
Type assetType = prefab.GetType(); | |
if (assetType == typeof(BuildingInfo)) | |
building(prefab as BuildingInfo, fileName); | |
else if (assetType == typeof(PropInfo)) | |
prop(prefab as PropInfo, fileName); | |
else if (assetType == typeof(TreeInfo)) | |
tree(prefab as TreeInfo, fileName); | |
else if (assetType == typeof(VehicleInfo)) | |
vehicle(prefab as VehicleInfo, fileName); | |
} | |
private Material GetMaterialFromSelected() | |
{ | |
if (assetType == "building") | |
return selectedBuilding.m_material; | |
else if (assetType == "prop") | |
return selectedProp.m_material; | |
else if (assetType == "tree") | |
return selectedTree.m_material; | |
else if (assetType == "vehicle") | |
return selectedVehicle.m_material; | |
return null; | |
} | |
private void ReplaceMesh(string type, string filename) | |
{ | |
if (type == "building") | |
{ | |
Mesh replacementMesh; | |
if (TryGetMesh(out replacementMesh, filename, false)) selectedBuilding.m_mesh = replacementMesh; | |
} | |
else if (type == "prop") | |
{ | |
Mesh replacementMesh; | |
if (TryGetMesh(out replacementMesh, filename, false)) selectedProp.m_mesh = replacementMesh; | |
} | |
else if (type == "tree") | |
{ | |
Mesh replacementMesh; | |
if (TryGetMesh(out replacementMesh, filename, false)) selectedTree.m_mesh = replacementMesh; | |
} | |
else if (type == "vehicle") | |
{ | |
Mesh replacementMesh; | |
if (TryGetMesh(out replacementMesh, filename, true)) selectedVehicle.m_mesh = replacementMesh; | |
} | |
} | |
private void CallReplaceTextures(string type, string filename, TexturesReplacement replace) | |
{ | |
if (type == "building") | |
{ | |
ReplaceTextures(selectedBuilding.m_material, type, filename, replace); | |
} | |
else if (type == "prop") | |
{ | |
ReplaceTextures(selectedProp.m_material, type, filename, replace); | |
if (selectedProp.m_isDecal) | |
ReplaceTextures(selectedProp.m_lodMaterialCombined, type, filename, replace); | |
} | |
else if (type == "tree") | |
{ | |
ReplaceTextures(selectedTree.m_material, type, filename, replace); | |
} | |
else if (type == "vehicle") | |
{ | |
ReplaceTextures(selectedVehicle.m_material, type, filename, replace); | |
} | |
} | |
// Actual logic (Ronyx69) | |
public static void building(BuildingInfo asset, string filename) | |
{ | |
Debug.Log("building called"); | |
if (asset != null) | |
{ | |
ReplaceTextures(asset.m_material, "building", filename, TexturesReplacement.Diffuse); | |
ReplaceTextures(asset.m_material, "building", filename, TexturesReplacement.Alpha); | |
ReplaceTextures(asset.m_material, "building", filename, TexturesReplacement.Color); | |
ReplaceTextures(asset.m_material, "building", filename, TexturesReplacement.Illumination); | |
ReplaceTextures(asset.m_material, "building", filename, TexturesReplacement.Normal); | |
ReplaceTextures(asset.m_material, "building", filename, TexturesReplacement.Specular); | |
Mesh replacementMesh; | |
if (TryGetMesh(out replacementMesh, filename, false)) asset.m_mesh = replacementMesh; | |
} | |
} | |
public static void prop(PropInfo asset, string filename) | |
{ | |
Debug.Log("prop called"); | |
if (asset != null) | |
{ | |
ReplaceTextures(asset.m_material, "prop", filename, TexturesReplacement.Diffuse); | |
ReplaceTextures(asset.m_material, "prop", filename, TexturesReplacement.Alpha); | |
ReplaceTextures(asset.m_material, "prop", filename, TexturesReplacement.Color); | |
ReplaceTextures(asset.m_material, "prop", filename, TexturesReplacement.Illumination); | |
ReplaceTextures(asset.m_material, "prop", filename, TexturesReplacement.Normal); | |
ReplaceTextures(asset.m_material, "prop", filename, TexturesReplacement.Specular); | |
if (asset.m_isDecal) | |
{ | |
ReplaceTextures(asset.m_lodMaterialCombined, "prop", filename, TexturesReplacement.Diffuse); | |
ReplaceTextures(asset.m_lodMaterialCombined, "prop", filename, TexturesReplacement.Alpha); | |
ReplaceTextures(asset.m_lodMaterialCombined, "prop", filename, TexturesReplacement.Color); | |
ReplaceTextures(asset.m_lodMaterialCombined, "prop", filename, TexturesReplacement.Illumination); | |
ReplaceTextures(asset.m_lodMaterialCombined, "prop", filename, TexturesReplacement.Normal); | |
ReplaceTextures(asset.m_lodMaterialCombined, "prop", filename, TexturesReplacement.Specular); | |
} | |
Mesh replacementMesh; | |
if (TryGetMesh(out replacementMesh, filename, false)) asset.m_mesh = replacementMesh; | |
} | |
} | |
public static void vehicle(VehicleInfo asset, string filename) | |
{ | |
Debug.Log("vehicle called"); | |
if (asset != null) | |
{ | |
ReplaceTextures(asset.m_material, "vehicle", filename, TexturesReplacement.Diffuse); | |
ReplaceTextures(asset.m_material, "vehicle", filename, TexturesReplacement.Alpha); | |
ReplaceTextures(asset.m_material, "vehicle", filename, TexturesReplacement.Color); | |
ReplaceTextures(asset.m_material, "vehicle", filename, TexturesReplacement.Illumination); | |
ReplaceTextures(asset.m_material, "vehicle", filename, TexturesReplacement.Normal); | |
ReplaceTextures(asset.m_material, "vehicle", filename, TexturesReplacement.Specular); | |
Mesh replacementMesh; | |
if (TryGetMesh(out replacementMesh, filename, true)) asset.m_mesh = replacementMesh; | |
} | |
} | |
public static void tree(TreeInfo asset, string filename) | |
{ | |
Debug.Log("tree called"); | |
if (asset != null) | |
{ | |
ReplaceTextures(asset.m_material, "tree", filename, TexturesReplacement.Diffuse); | |
ReplaceTextures(asset.m_renderMaterial, "tree", filename, TexturesReplacement.Diffuse); | |
ReplaceTextures(asset.m_material, "tree", filename, TexturesReplacement.Normal); | |
ReplaceTextures(asset.m_renderMaterial, "tree", filename, TexturesReplacement.Normal); | |
ReplaceTextures(asset.m_material, "tree", filename, TexturesReplacement.Color); | |
ReplaceTextures(asset.m_renderMaterial, "tree", filename, TexturesReplacement.Color); | |
ReplaceTextures(asset.m_material, "tree", filename, TexturesReplacement.Alpha); | |
ReplaceTextures(asset.m_renderMaterial, "tree", filename, TexturesReplacement.Alpha); | |
Mesh replacementMesh; | |
if (TryGetMesh(out replacementMesh, filename, false)) asset.m_mesh = replacementMesh; | |
} | |
} | |
public static void ReplaceTextures(Material material, string type, string filename, TexturesReplacement replace) | |
{ | |
Texture2D textureD = null, textureA = null, textureC = null, textureI = null, textureN = null, textureS = null; | |
Texture2D providedTexture = null; Color px; var texturePath = " "; | |
bool _d, _a, _c, _i, _n, _s, aci, xys, xyca, gamma; | |
_d = _a = _c = _i = _n = _s = aci = xys = xyca = gamma = false; | |
var path = ColossalFramework.IO.DataLocation.addonsPath + "\\Import\\" + filename; | |
if (type == "tree") | |
{ | |
var defaultX = 0.5f; var defaultY = 0.5f; var defaultC = 1f; var defaultA = 1f; | |
if (replace == TexturesReplacement.Diffuse) // _d (Diffuse) | |
{ | |
textureD = new Texture2D(1, 1); | |
texturePath = path + "_d.png"; | |
Debug.Log(texturePath); | |
if (File.Exists(texturePath)) | |
{ | |
textureD.LoadImage(File.ReadAllBytes(texturePath)); | |
_d = true; | |
} | |
if (_d) material.SetTexture("_MainTex", textureD); | |
} | |
if (replace == TexturesReplacement.Normal) // _n (Normal) | |
{ | |
textureN = new Texture2D(1, 1); | |
texturePath = path + "_n.png"; | |
if (File.Exists(texturePath)) | |
{ | |
textureN.LoadImage(File.ReadAllBytes(texturePath)); | |
providedTexture = textureN; _n = true; | |
} | |
} | |
if (replace == TexturesReplacement.Color) // _c (Color) | |
{ | |
textureC = new Texture2D(1, 1); | |
texturePath = path + "_c.png"; | |
if (File.Exists(texturePath)) | |
{ | |
textureC.LoadImage(File.ReadAllBytes(texturePath)); | |
providedTexture = textureC; _c = true; | |
} | |
} | |
if (replace == TexturesReplacement.Alpha) // _a (Alpha) | |
{ | |
textureA = new Texture2D(1, 1); | |
texturePath = path + "_a.png"; | |
if (File.Exists(texturePath)) | |
{ | |
textureA.LoadImage(File.ReadAllBytes(texturePath)); | |
providedTexture = textureA; _a = true; | |
} | |
} | |
if (replace == TexturesReplacement.Normal || replace == TexturesReplacement.Color || replace == TexturesReplacement.Alpha) // previous XYCA | |
{ | |
if (material.GetTexture("_XYCAMap")) | |
{ | |
var preXYCA = material.GetTexture("_XYCAMap") as Texture2D; | |
if (providedTexture) providedTexture = new Texture2D(providedTexture.width, providedTexture.height); | |
else providedTexture = new Texture2D(preXYCA.width, preXYCA.height); | |
if (preXYCA.name != "replaced") | |
{ | |
gamma = true; | |
} | |
providedTexture.SetPixels(preXYCA.GetPixels()); | |
providedTexture.anisoLevel = preXYCA.anisoLevel; | |
providedTexture.mipMapBias = preXYCA.mipMapBias; | |
providedTexture.filterMode = preXYCA.filterMode; | |
providedTexture.wrapMode = preXYCA.wrapMode; | |
providedTexture.Apply(); | |
xyca = true; GameObject.Destroy(preXYCA); | |
} | |
} | |
if (replace == TexturesReplacement.Normal || replace == TexturesReplacement.Color || replace == TexturesReplacement.Alpha) // XYCA COMBINATION | |
{ | |
if (providedTexture) | |
{ | |
for (int i = 0; i < providedTexture.width; i++) | |
{ | |
for (int j = 0; j < providedTexture.height; j++) | |
{ | |
px = providedTexture.GetPixel(i, j); | |
if (_n) | |
{ | |
px.r = textureN.GetPixel(i, j).r; | |
px.g = textureN.GetPixel(i, j).g; | |
} | |
else if (!xyca) | |
{ | |
px.r = defaultX; | |
px.g = defaultY; | |
} | |
if (_c) px.b = textureC.GetPixel(i, j).r; else if (!xyca) px.b = defaultC; | |
if (_a) px.a = textureA.GetPixel(i, j).r; else if (!xyca) px.a = defaultA; | |
if (_n || gamma) px.r = Mathf.LinearToGammaSpace(px.r); | |
if (_n || gamma) px.g = Mathf.LinearToGammaSpace(px.g); | |
if (_c) px.b = 1 - px.b; | |
if (_c || gamma) px.b = Mathf.LinearToGammaSpace(px.b); | |
if (_a) px.a = 1 - px.a; | |
providedTexture.SetPixel(i, j, px); | |
} | |
} | |
providedTexture.Apply(); providedTexture.name = "replaced"; | |
material.SetTexture("_XYCAMap", providedTexture); | |
} | |
} | |
} | |
else // if building/prop/vehicle | |
{ | |
var defaultA = 1f; var defaultC = 1f; var defaultI = 0f; | |
var defaultX = 0.5f; var defaultY = 0.5f; var defaultS = 0f; | |
if (type == "building") defaultI = 0.75f; | |
if (type == "vehicle") defaultI = 0.5f; | |
if (replace == TexturesReplacement.Diffuse) // _d (Diffuse) | |
{ | |
textureD = new Texture2D(1, 1); | |
texturePath = path + "_d.png"; | |
Debug.Log(texturePath); | |
if (File.Exists(texturePath)) | |
{ | |
textureD.LoadImage(File.ReadAllBytes(texturePath)); | |
_d = true; | |
} | |
if (_d) material.SetTexture("_MainTex", textureD); | |
} | |
// ACI START | |
if (replace == TexturesReplacement.Alpha) // _a (Alpha) | |
{ | |
textureA = new Texture2D(1, 1); | |
texturePath = path + "_a.png"; | |
if (File.Exists(texturePath)) | |
{ | |
textureA.LoadImage(File.ReadAllBytes(texturePath)); | |
providedTexture = textureA; _a = true; | |
} | |
} | |
if (replace == TexturesReplacement.Color) // _c (Color) | |
{ | |
textureC = new Texture2D(1, 1); | |
texturePath = path + "_c.png"; | |
if (File.Exists(texturePath)) | |
{ | |
textureC.LoadImage(File.ReadAllBytes(texturePath)); | |
providedTexture = textureC; _c = true; | |
} | |
} | |
if (replace == TexturesReplacement.Illumination) // _i (Illumination) | |
{ | |
textureI = new Texture2D(1, 1); | |
texturePath = path + "_i.png"; | |
if (File.Exists(texturePath)) | |
{ | |
textureI.LoadImage(File.ReadAllBytes(texturePath)); | |
providedTexture = textureI; _i = true; | |
} | |
} | |
if (replace == TexturesReplacement.Alpha || replace == TexturesReplacement.Color || replace == TexturesReplacement.Illumination) // previous ACI | |
{ | |
if (material.GetTexture("_ACIMap")) | |
{ | |
var preACI = material.GetTexture("_ACIMap") as Texture2D; | |
if (providedTexture) providedTexture = new Texture2D(providedTexture.width, providedTexture.height); | |
else providedTexture = new Texture2D(preACI.width, preACI.height); | |
if (preACI.name != "replaced") | |
{ | |
gamma = true; | |
} | |
providedTexture.SetPixels(preACI.GetPixels()); | |
providedTexture.anisoLevel = preACI.anisoLevel; | |
providedTexture.mipMapBias = preACI.mipMapBias; | |
providedTexture.filterMode = preACI.filterMode; | |
providedTexture.wrapMode = preACI.wrapMode; | |
providedTexture.Apply(); | |
aci = true; GameObject.Destroy(preACI); | |
} | |
} | |
if (replace == TexturesReplacement.Alpha || replace == TexturesReplacement.Color || replace == TexturesReplacement.Illumination) // ACI COMBINATION | |
{ | |
if (providedTexture) | |
{ | |
for (int i = 0; i < providedTexture.width; i++) | |
{ | |
for (int j = 0; j < providedTexture.height; j++) | |
{ | |
px = providedTexture.GetPixel(i, j); | |
if (_a) px.r = textureA.GetPixel(i, j).r; | |
else if (!aci) px.r = defaultA; | |
if (_c) px.g = textureC.GetPixel(i, j).g; else if (!aci) px.g = defaultC; | |
if (_i) px.b = textureI.GetPixel(i, j).b; else if (!aci) px.b = defaultI; | |
if (_a) px.r = 1 - px.r; | |
if (gamma || _a) px.r = Mathf.LinearToGammaSpace(px.r); | |
if (_c) px.g = 1 - px.g; | |
if (gamma || _c) px.g = Mathf.LinearToGammaSpace(px.g); | |
if (gamma || _i) px.b = Mathf.LinearToGammaSpace(px.b); | |
providedTexture.SetPixel(i, j, px); | |
} | |
} | |
providedTexture.Apply(); providedTexture.name = "replaced"; | |
material.SetTexture("_ACIMap", providedTexture); | |
} | |
} | |
// XYS START | |
providedTexture = null; gamma = false; | |
if (replace == TexturesReplacement.Normal) // _n (Normal) | |
{ | |
textureN = new Texture2D(1, 1); | |
texturePath = path + "_n.png"; | |
if (File.Exists(texturePath)) | |
{ | |
textureN.LoadImage(File.ReadAllBytes(texturePath)); | |
providedTexture = textureN; _n = true; | |
} | |
} | |
if (replace == TexturesReplacement.Specular) // _s (Specular) | |
{ | |
textureS = new Texture2D(1, 1); | |
texturePath = path + "_s.png"; | |
if (File.Exists(texturePath)) | |
{ | |
textureS.LoadImage(File.ReadAllBytes(texturePath)); | |
providedTexture = textureS; _s = true; | |
} | |
} | |
if (replace == TexturesReplacement.Normal || replace == TexturesReplacement.Specular) // previous XYS | |
{ | |
if (material.GetTexture("_XYSMap")) | |
{ | |
var preXYS = material.GetTexture("_XYSMap") as Texture2D; | |
if (providedTexture) providedTexture = new Texture2D(providedTexture.width, providedTexture.height); | |
else providedTexture = new Texture2D(preXYS.width, preXYS.height); | |
if (preXYS.name != "replaced") | |
{ | |
gamma = true; | |
} | |
providedTexture.SetPixels(preXYS.GetPixels()); | |
providedTexture.anisoLevel = preXYS.anisoLevel; | |
providedTexture.mipMapBias = preXYS.mipMapBias; | |
providedTexture.filterMode = preXYS.filterMode; | |
providedTexture.wrapMode = preXYS.wrapMode; | |
providedTexture.Apply(); | |
xys = true; GameObject.Destroy(preXYS); | |
} | |
} | |
if (replace == TexturesReplacement.Normal || replace == TexturesReplacement.Specular) // XYS COMBINATION | |
{ | |
if (providedTexture) | |
{ | |
for (int i = 0; i < providedTexture.width; i++) | |
{ | |
for (int j = 0; j < providedTexture.height; j++) | |
{ | |
px = providedTexture.GetPixel(i, j); | |
if (_n) | |
{ | |
px.r = textureN.GetPixel(i, j).r; | |
px.g = textureN.GetPixel(i, j).g; | |
} | |
else if (!xys) | |
{ | |
px.r = defaultX; | |
px.g = defaultY; | |
} | |
if (_s) px.b = textureS.GetPixel(i, j).b; else if (!xys) px.b = defaultS; | |
if (_n || gamma) px.r = Mathf.LinearToGammaSpace(px.r); | |
if (_n || gamma) px.g = Mathf.LinearToGammaSpace(px.g); | |
if (_s) px.b = 1 - px.b; | |
if (_s || gamma) px.b = Mathf.LinearToGammaSpace(px.b); | |
providedTexture.SetPixel(i, j, px); | |
} | |
} | |
providedTexture.Apply(); providedTexture.name = "replaced"; | |
material.SetTexture("_XYSMap", providedTexture); | |
} | |
} | |
} | |
} | |
public static bool TryGetMesh(out Mesh newMesh, string filename, bool isVehicle) | |
{ | |
string path = "none"; | |
if (File.Exists(ColossalFramework.IO.DataLocation.addonsPath + "\\Import\\" + filename + ".obj")) path = ColossalFramework.IO.DataLocation.addonsPath + "\\Import\\" + filename + ".obj"; | |
// else if (File.Exists(ColossalFramework.IO.DataLocation.addonsPath + "\\Import\\" + assetname + ".obj")) path = ColossalFramework.IO.DataLocation.addonsPath + "\\Import\\" + assetname + ".obj"; | |
if (path != "none") | |
{ | |
Mesh mesh = new Mesh(); | |
using (FileStream fileStream = File.Open(path, FileMode.Open)) | |
{ | |
mesh.LoadOBJ(OBJLoader.LoadOBJ(fileStream)); | |
} | |
if (isVehicle) | |
{ | |
Color nc = new Color(0, 0, 255); | |
Vector3[] vertices = mesh.vertices; | |
Color[] colors = new Color[vertices.Length]; | |
for (int i = 0; i < vertices.Length; i++) colors[i] = nc; | |
mesh.colors = colors; | |
} | |
newMesh = mesh; | |
return true; | |
} | |
newMesh = null; | |
return false; | |
} | |
} | |
// Enum | |
public enum TexturesReplacement { Diffuse, Alpha, Color, Illumination, Normal, Specular } | |
// Utils Class | |
public static class RealtimeUtils | |
{ | |
public static PrefabInfo SelectedAsPrefabInfo(this RealtimeUI logic) | |
{ | |
switch (logic.assetType) | |
{ | |
case "prop": | |
return logic.selectedProp; | |
case "building": | |
return logic.selectedBuilding; | |
case "tree": | |
return logic.selectedTree; | |
case "vehicle": | |
return logic.selectedVehicle; | |
} | |
return null; | |
} | |
public static string RemoveMissingTranslationString(this string baseString) | |
{ | |
return baseString.Replace("BUILDING_TITLE[", "").Replace("PROPS_TITLE[", "").Replace("]:0", ""); | |
} | |
public static void UIReplace<T>(Vector2 position, string title, string shortReplaceButton, string filePrefix, string fileSuffix, string filesDirectory, Action<T> replaceAction, T param1) | |
{ | |
GUI.Label(new Rect(position.x, position.y + 3, 330, 23), "<b>" + title + ":</b> " + filePrefix + fileSuffix); | |
if (filePrefix.IsWindowsRestrictedFile()) | |
{ | |
GUI.color = Color.red; | |
GUI.Label(new Rect(position.x + 340, position.y, 120, 27), "[INVALID NAME]", GUI.skin.button); | |
GUI.color = Color.white; | |
} | |
else if (File.Exists(filesDirectory + filePrefix + fileSuffix)) | |
{ | |
Debug.Log(filesDirectory + filePrefix + fileSuffix); | |
if (GUI.Button(new Rect(position.x + 340, position.y, 120, 27), "Replace " + shortReplaceButton)) | |
{ | |
replaceAction.Invoke(param1); | |
} | |
} | |
else | |
{ | |
GUI.color = Color.red; | |
GUI.Label(new Rect(position.x + 340, position.y, 120, 27), "[FILE MISSING]", GUI.skin.button); | |
GUI.color = Color.white; | |
} | |
} | |
public static void UIReplace<T1, T2>(Vector2 position, string title, string shortReplaceButton, string filePrefix, string fileSuffix, string filesDirectory, Action<T1, T2> replaceAction, T1 param1, T2 param2) | |
{ | |
GUI.Label(new Rect(position.x, position.y + 3, 330, 23), "<b>" + title + ":</b> " + filePrefix + fileSuffix); | |
if (filePrefix.IsWindowsRestrictedFile()) | |
{ | |
GUI.color = Color.red; | |
GUI.Label(new Rect(position.x + 340, position.y, 120, 27), "[INVALID NAME]", GUI.skin.button); | |
GUI.color = Color.white; | |
} | |
else if (File.Exists(filesDirectory + filePrefix + fileSuffix)) | |
{ | |
Debug.Log(filesDirectory + filePrefix + fileSuffix); | |
if (GUI.Button(new Rect(position.x + 340, position.y, 120, 27), "Replace " + shortReplaceButton)) | |
{ | |
replaceAction.Invoke(param1, param2); | |
} | |
} | |
else | |
{ | |
GUI.color = Color.red; | |
GUI.Label(new Rect(position.x + 340, position.y, 120, 27), "[FILE MISSING]", GUI.skin.button); | |
GUI.color = Color.white; | |
} | |
} | |
public static void UIReplace<T1, T2, T3>(Vector2 position, string title, string shortReplaceButton, string filePrefix, string fileSuffix, string filesDirectory, Action<T1, T2, T3> replaceAction, T1 param1, T2 param2, T3 param3) | |
{ | |
GUI.Label(new Rect(position.x, position.y + 3, 330, 23), "<b>" + title + ":</b> " + filePrefix + fileSuffix); | |
if (filePrefix.IsWindowsRestrictedFile()) | |
{ | |
GUI.color = Color.red; | |
GUI.Label(new Rect(position.x + 340, position.y, 120, 27), "[INVALID NAME]", GUI.skin.button); | |
GUI.color = Color.white; | |
} | |
else if (File.Exists(filesDirectory + filePrefix + fileSuffix)) | |
{ | |
Debug.Log(filesDirectory + filePrefix + fileSuffix); | |
if (GUI.Button(new Rect(position.x + 340, position.y, 120, 27), "Replace " + shortReplaceButton)) | |
{ | |
replaceAction.Invoke(param1, param2, param3); | |
} | |
} | |
else | |
{ | |
GUI.color = Color.red; | |
GUI.Label(new Rect(position.x + 340, position.y, 120, 27), "[FILE MISSING]", GUI.skin.button); | |
GUI.color = Color.white; | |
} | |
} | |
public static void UIReplace<T1, T2, T3, T4>(Vector2 position, string title, string shortReplaceButton, string filePrefix, string fileSuffix, string filesDirectory, Action<T1, T2, T3, T4> replaceAction, T1 param1, T2 param2, T3 param3, T4 param4) | |
{ | |
GUI.Label(new Rect(position.x, position.y + 3, 330, 23), "<b>" + title + ":</b> " + filePrefix + fileSuffix); | |
if (filePrefix.IsWindowsRestrictedFile()) | |
{ | |
GUI.color = Color.red; | |
GUI.Label(new Rect(position.x + 340, position.y, 120, 27), "[INVALID NAME]", GUI.skin.button); | |
GUI.color = Color.white; | |
} | |
else if (File.Exists(filesDirectory + filePrefix + fileSuffix)) | |
{ | |
Debug.Log(filesDirectory + filePrefix + fileSuffix); | |
if (GUI.Button(new Rect(position.x + 340, position.y, 120, 27), "Replace " + shortReplaceButton)) | |
{ | |
replaceAction.Invoke(param1, param2, param3, param4); | |
} | |
} | |
else | |
{ | |
GUI.color = Color.red; | |
GUI.Label(new Rect(position.x + 340, position.y, 120, 27), "[FILE MISSING]", GUI.skin.button); | |
GUI.color = Color.white; | |
} | |
} | |
public static void UIReplaceUnavailable(Vector2 position, string title, string mode) | |
{ | |
GUI.Label(new Rect(position.x, position.y + 3, 330, 23), "<b>" + title + ":</b> <i>Unavailable for " + mode + "</i>"); | |
} | |
public static void ResetTool() | |
{ | |
ToolsModifierControl.toolController.CurrentTool = ToolsModifierControl.GetTool<DefaultTool>(); | |
ToolsModifierControl.SetTool<DefaultTool>(); | |
ToolsModifierControl.mainToolbar.CloseEverything(); | |
} | |
public static bool IsWindowsRestrictedFile(this string checkString) | |
{ | |
string str = checkString.ToLower(); | |
return (str == "aux") || (str == "con") || (str == "com1") || (str == "com2") || (str == "com3") || (str == "com4") || (str == "lpt1") || (str == "lpt2") || (str == "lpt3") || (str == "ptn") || (str == "nul"); | |
} | |
public static bool IsDroppableToolEnabled() | |
{ | |
Type toolType = ToolsModifierControl.toolController.CurrentTool.GetType(); | |
return ((toolType == typeof(PropTool)) || (toolType == typeof(BuildingTool)) || (toolType == typeof(TreeTool))); | |
} | |
public static string PossessionApostrophe(this string baseString) | |
{ | |
if (baseString.ToLower()[baseString.Length - 1] == 's') | |
return baseString += "'"; | |
else | |
return baseString += "'s"; | |
} | |
public static bool AnyFileExists(string fileLocation, string filePrefix, params string[] filesSuffixes) | |
{ | |
if (filePrefix.IsWindowsRestrictedFile()) | |
return false; | |
return filesSuffixes.Any(suffix => File.Exists(fileLocation + filePrefix + suffix)); | |
} | |
public static string[] AllFilesExtensions(string assetType) | |
{ | |
if (assetType == "tree") | |
return new string[] { ".obj", "_d.png", "_a.png", "_n.png", "_c.png" }; | |
else | |
return new string[] { ".obj", "_d.png", "_a.png", "_n.png", "_s.png", "_i.png", "_c.png" }; | |
} | |
public static string ExtractFilename(this string fullPrefabname) | |
{ | |
if (fullPrefabname.Contains(".")) | |
return fullPrefabname.Split(new string[] { "." }, StringSplitOptions.RemoveEmptyEntries)[0]; | |
else | |
return fullPrefabname; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There is a problem with all linux users, linux uses (and maybe also mac) normal slash to separete folders, while windows uses backslashes, so, sentences like
FilesLocation = DataLocation.addonsPath + "\Import\";
fails on linux, so, to correct it, there is a constant on System.IO to address the correct path separator, i can't compile right now, but changing this all mac and linux users will be gratefull tu use your mod.
I think someting like
FilesLocation = DataLocation.addonsPath + Path.PathSeparator + "Import" + Path.PathSeparator;
could do the job
below i show the error message that we (linux users) have, i wish it could be helpful