mirror of
https://github.com/batfasturd/BetterNPCEditor.git
synced 2025-06-06 18:31:05 +00:00
First commit.
This commit is contained in:
commit
34e44def21
400
.gitignore
vendored
Normal file
400
.gitignore
vendored
Normal file
@ -0,0 +1,400 @@
|
||||
# ---> VisualStudio
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
# tools/**
|
||||
# !tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# JetBrains Rider
|
||||
*.sln.iml
|
||||
|
25
Better NCP Editor.sln
Normal file
25
Better NCP Editor.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.35806.99 d17.13
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Better NCP Editor", "Better NCP Editor\Better NCP Editor.csproj", "{F8476FF1-BC01-4E29-8173-BAE156F04A7D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F8476FF1-BC01-4E29-8173-BAE156F04A7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F8476FF1-BC01-4E29-8173-BAE156F04A7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F8476FF1-BC01-4E29-8173-BAE156F04A7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F8476FF1-BC01-4E29-8173-BAE156F04A7D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {1B1849C0-03C6-4E31-90CB-59B8DDE61F4D}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
12
Better NCP Editor/Better NCP Editor.csproj
Normal file
12
Better NCP Editor/Better NCP Editor.csproj
Normal file
@ -0,0 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<RootNamespace>Better_NCP_Editor</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
354
Better NCP Editor/BetterNPCEntity.cs
Normal file
354
Better NCP Editor/BetterNPCEntity.cs
Normal file
@ -0,0 +1,354 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Better_NCP_Editor
|
||||
{
|
||||
public class BetterNPCEntity
|
||||
{
|
||||
// In BetterNPCEntity class
|
||||
public BetterNPCEntity()
|
||||
{
|
||||
Enabled = false;
|
||||
MonumentSize = "";
|
||||
RemoveOtherNPCs = false;
|
||||
Presets = new List<Preset>();
|
||||
}
|
||||
|
||||
[JsonPropertyName("Enabled? [true/false]")]
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
// Marked as nullable since some JSON files may not include this key.
|
||||
[JsonPropertyName("The size of the monument")]
|
||||
public string? MonumentSize { get; set; }
|
||||
|
||||
// Marked as nullable so a missing key won’t cause issues.
|
||||
[JsonPropertyName("Remove other NPCs? [true/false]")]
|
||||
public bool? RemoveOtherNPCs { get; set; }
|
||||
|
||||
[JsonPropertyName("Presets")]
|
||||
public List<Preset> Presets { get; set; }
|
||||
}
|
||||
|
||||
public class Preset
|
||||
{
|
||||
// In Preset class
|
||||
public Preset()
|
||||
{
|
||||
Enabled = true;
|
||||
MinimumNumbersDay = 1;
|
||||
MaximumNumbersDay = 1;
|
||||
MinimumNumbersNight = 1;
|
||||
MaximumNumbersNight = 1;
|
||||
NPCSetting = new NPCSetting();
|
||||
Economics = new EconomicsInfo();
|
||||
AppearanceType = 0;
|
||||
OwnLocations = new List<string>();
|
||||
ReturnToAppearance = false;
|
||||
NavigationGridType = 0;
|
||||
CratePath = "";
|
||||
LootTableType = 0;
|
||||
PrefabLootTable = new PrefabLootTable();
|
||||
OwnLootTable = new OwnLootTable();
|
||||
}
|
||||
|
||||
[JsonPropertyName("Enabled? [true/false]")]
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
[JsonPropertyName("Minimum numbers - Day")]
|
||||
public int MinimumNumbersDay { get; set; }
|
||||
|
||||
[JsonPropertyName("Maximum numbers - Day")]
|
||||
public int MaximumNumbersDay { get; set; }
|
||||
|
||||
[JsonPropertyName("Minimum numbers - Night")]
|
||||
public int MinimumNumbersNight { get; set; }
|
||||
|
||||
[JsonPropertyName("Maximum numbers - Night")]
|
||||
public int MaximumNumbersNight { get; set; }
|
||||
|
||||
[JsonPropertyName("NPCs setting")]
|
||||
public NPCSetting NPCSetting { get; set; }
|
||||
|
||||
[JsonPropertyName("The amount of economics that is given for killing the NPC")]
|
||||
public EconomicsInfo Economics { get; set; }
|
||||
|
||||
[JsonPropertyName("Type of appearance (0 - random; 1 - own list) (not used for Road and Biome)")]
|
||||
public int AppearanceType { get; set; }
|
||||
|
||||
[JsonPropertyName("Own list of locations (not used for Road and Biome)")]
|
||||
public List<string> OwnLocations { get; set; }
|
||||
|
||||
[JsonPropertyName("If the NPC ends up below ocean sea level, should the NPC return to it's place of appearance? [true/false]")]
|
||||
public bool ReturnToAppearance { get; set; }
|
||||
|
||||
[JsonPropertyName("Type of navigation grid (0 - used mainly on the island, 1 - used mainly under water or under land, as well as outside the map, can be used on some monuments)")]
|
||||
public int NavigationGridType { get; set; }
|
||||
|
||||
[JsonPropertyName("The path to the crate that appears at the place of death (empty - not used)")]
|
||||
public string CratePath { get; set; }
|
||||
|
||||
[JsonPropertyName("Which loot table should the plugin use? (0 - default; 1 - own; 2 - AlphaLoot; 3 - CustomLoot; 4 - loot table of the Rust objects; 5 - combine the 1 and 4 methods)")]
|
||||
public int LootTableType { get; set; }
|
||||
|
||||
[JsonPropertyName("Loot table from prefabs (if the loot table type is 4 or 5)")]
|
||||
public PrefabLootTable PrefabLootTable { get; set; }
|
||||
|
||||
[JsonPropertyName("Own loot table (if the loot table type is 1 or 5)")]
|
||||
public OwnLootTable OwnLootTable { get; set; }
|
||||
}
|
||||
|
||||
public class NPCSetting
|
||||
{
|
||||
// In NPCSetting class
|
||||
public NPCSetting()
|
||||
{
|
||||
Names = new List<string>();
|
||||
Health = 100.0;
|
||||
RoamRange = 0.0;
|
||||
ChaseRange = 0.0;
|
||||
AttackRangeMultiplier = 1.0;
|
||||
SenseRange = 0.0;
|
||||
TargetMemoryDuration = 0.0;
|
||||
ScaleDamage = 1.0;
|
||||
AimConeScale = 1.0;
|
||||
DetectInVisionCone = false;
|
||||
VisionCone = 0.0;
|
||||
Speed = 0.0;
|
||||
MinAppearanceTime = 0.0;
|
||||
MaxAppearanceTime = 0.0;
|
||||
DisableRadioEffects = false;
|
||||
IsStationary = false;
|
||||
RemoveCorpse = false;
|
||||
WearItems = new List<WearItem>();
|
||||
BeltItems = new List<BeltItem>();
|
||||
Kits = new List<object>();
|
||||
}
|
||||
|
||||
[JsonPropertyName("Names")]
|
||||
public List<string> Names { get; set; }
|
||||
|
||||
[JsonPropertyName("Health")]
|
||||
public double Health { get; set; }
|
||||
|
||||
[JsonPropertyName("Roam Range")]
|
||||
public double RoamRange { get; set; }
|
||||
|
||||
[JsonPropertyName("Chase Range")]
|
||||
public double ChaseRange { get; set; }
|
||||
|
||||
[JsonPropertyName("Attack Range Multiplier")]
|
||||
public double AttackRangeMultiplier { get; set; }
|
||||
|
||||
[JsonPropertyName("Sense Range")]
|
||||
public double SenseRange { get; set; }
|
||||
|
||||
[JsonPropertyName("Target Memory Duration [sec.]")]
|
||||
public double TargetMemoryDuration { get; set; }
|
||||
|
||||
[JsonPropertyName("Scale damage")]
|
||||
public double ScaleDamage { get; set; }
|
||||
|
||||
[JsonPropertyName("Aim Cone Scale")]
|
||||
public double AimConeScale { get; set; }
|
||||
|
||||
[JsonPropertyName("Detect the target only in the NPC's viewing vision cone? [true/false]")]
|
||||
public bool DetectInVisionCone { get; set; }
|
||||
|
||||
[JsonPropertyName("Vision Cone")]
|
||||
public double VisionCone { get; set; }
|
||||
|
||||
[JsonPropertyName("Speed")]
|
||||
public double Speed { get; set; }
|
||||
|
||||
[JsonPropertyName("Minimum time of appearance after death (not used for Events) [sec.]")]
|
||||
public double MinAppearanceTime { get; set; }
|
||||
|
||||
[JsonPropertyName("Maximum time of appearance after death (not used for Events) [sec.]")]
|
||||
public double MaxAppearanceTime { get; set; }
|
||||
|
||||
[JsonPropertyName("Disable radio effects? [true/false]")]
|
||||
public bool DisableRadioEffects { get; set; }
|
||||
|
||||
[JsonPropertyName("Is this a stationary NPC? [true/false]")]
|
||||
public bool IsStationary { get; set; }
|
||||
|
||||
[JsonPropertyName("Remove a corpse after death? (it is recommended to use the true value to improve performance) [true/false]")]
|
||||
public bool RemoveCorpse { get; set; }
|
||||
|
||||
[JsonPropertyName("Wear items")]
|
||||
public List<WearItem> WearItems { get; set; }
|
||||
|
||||
[JsonPropertyName("Belt items")]
|
||||
public List<BeltItem> BeltItems { get; set; }
|
||||
|
||||
[JsonPropertyName("Kits (it is recommended to use the previous 2 settings to improve performance)")]
|
||||
public List<object> Kits { get; set; }
|
||||
}
|
||||
|
||||
public class WearItem
|
||||
{
|
||||
// In WearItem class
|
||||
public WearItem()
|
||||
{
|
||||
ShortName = "";
|
||||
SkinID = 0;
|
||||
}
|
||||
|
||||
[JsonPropertyName("ShortName")]
|
||||
public string ShortName { get; set; }
|
||||
|
||||
[JsonPropertyName("SkinID (0 - default)")]
|
||||
public long SkinID { get; set; } // Changed from int to long
|
||||
}
|
||||
|
||||
public class BeltItem
|
||||
{
|
||||
// In BeltItem class
|
||||
public BeltItem()
|
||||
{
|
||||
ShortName = "";
|
||||
Amount = 0;
|
||||
SkinID = 0;
|
||||
Mods = new List<string>();
|
||||
Ammo = "";
|
||||
}
|
||||
|
||||
|
||||
[JsonPropertyName("ShortName")]
|
||||
public string ShortName { get; set; }
|
||||
|
||||
[JsonPropertyName("Amount")]
|
||||
public int Amount { get; set; }
|
||||
|
||||
[JsonPropertyName("SkinID (0 - default)")]
|
||||
public long SkinID { get; set; } // Changed from int to long
|
||||
|
||||
[JsonPropertyName("Mods")]
|
||||
public List<string> Mods { get; set; }
|
||||
|
||||
[JsonPropertyName("Ammo")]
|
||||
public string Ammo { get; set; }
|
||||
}
|
||||
|
||||
public class LootItem
|
||||
{
|
||||
// In LootItem class
|
||||
public LootItem()
|
||||
{
|
||||
ShortName = "";
|
||||
Minimum = 0;
|
||||
Maximum = 0;
|
||||
Chance = 0.0;
|
||||
IsBlueprint = false;
|
||||
SkinID = 0;
|
||||
Name = "";
|
||||
}
|
||||
|
||||
[JsonPropertyName("ShortName")]
|
||||
public string ShortName { get; set; }
|
||||
|
||||
[JsonPropertyName("Minimum")]
|
||||
public int Minimum { get; set; }
|
||||
|
||||
[JsonPropertyName("Maximum")]
|
||||
public int Maximum { get; set; }
|
||||
|
||||
[JsonPropertyName("Chance [0.0-100.0]")]
|
||||
public double Chance { get; set; }
|
||||
|
||||
[JsonPropertyName("Is this a blueprint? [true/false]")]
|
||||
public bool IsBlueprint { get; set; }
|
||||
|
||||
[JsonPropertyName("SkinID (0 - default)")]
|
||||
public long SkinID { get; set; } // Changed from int to long
|
||||
|
||||
[JsonPropertyName("Name (empty - default)")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class EconomicsInfo
|
||||
{
|
||||
// In EconomicsInfo class
|
||||
public EconomicsInfo()
|
||||
{
|
||||
Economics = 0.0;
|
||||
ServerRewards = 0;
|
||||
IQEconomic = 0;
|
||||
}
|
||||
|
||||
[JsonPropertyName("Economics")]
|
||||
public double Economics { get; set; }
|
||||
|
||||
[JsonPropertyName("Server Rewards (minimum 1)")]
|
||||
public int ServerRewards { get; set; }
|
||||
|
||||
[JsonPropertyName("IQEconomic (minimum 1)")]
|
||||
public int IQEconomic { get; set; }
|
||||
}
|
||||
|
||||
public class PrefabLootTable
|
||||
{
|
||||
// In PrefabLootTable class
|
||||
public PrefabLootTable()
|
||||
{
|
||||
MinPrefabs = 0;
|
||||
MaxPrefabs = 0;
|
||||
UseMinMax = false;
|
||||
Prefabs = new List<Prefab>();
|
||||
}
|
||||
|
||||
|
||||
[JsonPropertyName("Minimum numbers of prefabs")]
|
||||
public int MinPrefabs { get; set; }
|
||||
|
||||
[JsonPropertyName("Maximum numbers of prefabs")]
|
||||
public int MaxPrefabs { get; set; }
|
||||
|
||||
[JsonPropertyName("Use minimum and maximum values? [true/false]")]
|
||||
public bool UseMinMax { get; set; }
|
||||
|
||||
[JsonPropertyName("List of prefabs")]
|
||||
public List<Prefab> Prefabs { get; set; }
|
||||
}
|
||||
|
||||
public class Prefab
|
||||
{
|
||||
// In Prefab class
|
||||
public Prefab()
|
||||
{
|
||||
Chance = 0.0;
|
||||
Path = "";
|
||||
}
|
||||
|
||||
[JsonPropertyName("Chance [0.0-100.0]")]
|
||||
public double Chance { get; set; }
|
||||
|
||||
[JsonPropertyName("The path to the prefab")]
|
||||
public string Path { get; set; }
|
||||
}
|
||||
|
||||
public class OwnLootTable
|
||||
{
|
||||
// In OwnLootTable class
|
||||
public OwnLootTable()
|
||||
{
|
||||
MinItems = 0;
|
||||
MaxItems = 0;
|
||||
UseMinMax = false;
|
||||
Items = new List<LootItem>();
|
||||
}
|
||||
|
||||
[JsonPropertyName("Minimum numbers of items")]
|
||||
public int MinItems { get; set; }
|
||||
|
||||
[JsonPropertyName("Maximum numbers of items")]
|
||||
public int MaxItems { get; set; }
|
||||
|
||||
[JsonPropertyName("Use minimum and maximum values? [true/false]")]
|
||||
public bool UseMinMax { get; set; }
|
||||
|
||||
[JsonPropertyName("List of items")]
|
||||
public List<LootItem> Items { get; set; }
|
||||
}
|
||||
|
||||
}
|
97
Better NCP Editor/EditValueForm.cs
Normal file
97
Better NCP Editor/EditValueForm.cs
Normal file
@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Better_NCP_Editor
|
||||
{
|
||||
public partial class EditValueForm : Form
|
||||
{
|
||||
public object NewValue { get; private set; }
|
||||
private Control inputControl;
|
||||
|
||||
public EditValueForm(string propertyName, string currentValue, Type valueType)
|
||||
{
|
||||
// Set form properties.
|
||||
this.Text = $"Edit {propertyName}";
|
||||
this.StartPosition = FormStartPosition.CenterParent;
|
||||
this.ClientSize = new Size(250, 120);
|
||||
|
||||
// Label for the property.
|
||||
Label lblProperty = new Label()
|
||||
{
|
||||
Text = propertyName,
|
||||
Location = new Point(10, 10),
|
||||
AutoSize = true
|
||||
};
|
||||
this.Controls.Add(lblProperty);
|
||||
|
||||
// Create the appropriate input control.
|
||||
if (valueType == typeof(bool))
|
||||
{
|
||||
ComboBox combo = new ComboBox()
|
||||
{
|
||||
Location = new Point(10, 40),
|
||||
Width = 200,
|
||||
DropDownStyle = ComboBoxStyle.DropDownList
|
||||
};
|
||||
combo.Items.Add("true");
|
||||
combo.Items.Add("false");
|
||||
combo.SelectedItem = currentValue;
|
||||
inputControl = combo;
|
||||
this.Controls.Add(combo);
|
||||
}
|
||||
else
|
||||
{
|
||||
TextBox txtBox = new TextBox()
|
||||
{
|
||||
Location = new Point(10, 40),
|
||||
Width = 200,
|
||||
Text = currentValue
|
||||
};
|
||||
inputControl = txtBox;
|
||||
this.Controls.Add(txtBox);
|
||||
}
|
||||
|
||||
// OK button.
|
||||
Button btnOk = new Button()
|
||||
{
|
||||
Text = "OK",
|
||||
Location = new Point(10, 80),
|
||||
DialogResult = DialogResult.OK
|
||||
};
|
||||
btnOk.Click += BtnOk_Click;
|
||||
this.Controls.Add(btnOk);
|
||||
|
||||
// Cancel button.
|
||||
Button btnCancel = new Button()
|
||||
{
|
||||
Text = "Cancel",
|
||||
Location = new Point(120, 80),
|
||||
DialogResult = DialogResult.Cancel
|
||||
};
|
||||
this.Controls.Add(btnCancel);
|
||||
|
||||
this.AcceptButton = btnOk;
|
||||
this.CancelButton = btnCancel;
|
||||
}
|
||||
|
||||
private void BtnOk_Click(object sender, EventArgs e)
|
||||
{
|
||||
// Determine new value based on input control.
|
||||
if (inputControl is ComboBox combo)
|
||||
{
|
||||
NewValue = combo.SelectedItem.ToString() == "true";
|
||||
}
|
||||
else if (inputControl is TextBox txt)
|
||||
{
|
||||
// Try to parse an integer; if it fails, treat it as a string.
|
||||
if (int.TryParse(txt.Text, out int intValue))
|
||||
NewValue = intValue;
|
||||
else
|
||||
NewValue = txt.Text;
|
||||
}
|
||||
this.DialogResult = DialogResult.OK;
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
}
|
120
Better NCP Editor/EditValueForm.resx
Normal file
120
Better NCP Editor/EditValueForm.resx
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
166
Better NCP Editor/Form1.Designer.cs
generated
Normal file
166
Better NCP Editor/Form1.Designer.cs
generated
Normal file
@ -0,0 +1,166 @@
|
||||
namespace Better_NCP_Editor
|
||||
{
|
||||
partial class Form1
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
btn_load = new Button();
|
||||
btn_save = new Button();
|
||||
dirTreeView = new TreeView();
|
||||
entityTreeView = new TreeView();
|
||||
btn_entity_del = new Button();
|
||||
btn_entity_add = new Button();
|
||||
btn_import_entityData = new Button();
|
||||
btn_export_entityData = new Button();
|
||||
statusTextbox = new TextBox();
|
||||
SuspendLayout();
|
||||
//
|
||||
// btn_load
|
||||
//
|
||||
btn_load.Location = new Point(17, 12);
|
||||
btn_load.Name = "btn_load";
|
||||
btn_load.Size = new Size(135, 29);
|
||||
btn_load.TabIndex = 0;
|
||||
btn_load.Text = "Load Directory";
|
||||
btn_load.UseVisualStyleBackColor = true;
|
||||
btn_load.Click += btn_load_Click;
|
||||
//
|
||||
// btn_save
|
||||
//
|
||||
btn_save.Enabled = false;
|
||||
btn_save.Location = new Point(214, 12);
|
||||
btn_save.Name = "btn_save";
|
||||
btn_save.Size = new Size(93, 29);
|
||||
btn_save.TabIndex = 1;
|
||||
btn_save.Text = "Save File";
|
||||
btn_save.UseVisualStyleBackColor = true;
|
||||
btn_save.Click += btn_save_Click;
|
||||
//
|
||||
// dirTreeView
|
||||
//
|
||||
dirTreeView.Location = new Point(17, 53);
|
||||
dirTreeView.Name = "dirTreeView";
|
||||
dirTreeView.Size = new Size(290, 812);
|
||||
dirTreeView.TabIndex = 2;
|
||||
//
|
||||
// entityTreeView
|
||||
//
|
||||
entityTreeView.Location = new Point(313, 53);
|
||||
entityTreeView.Name = "entityTreeView";
|
||||
entityTreeView.Size = new Size(843, 812);
|
||||
entityTreeView.TabIndex = 3;
|
||||
//
|
||||
// btn_entity_del
|
||||
//
|
||||
btn_entity_del.Enabled = false;
|
||||
btn_entity_del.Location = new Point(366, 12);
|
||||
btn_entity_del.Name = "btn_entity_del";
|
||||
btn_entity_del.Size = new Size(47, 29);
|
||||
btn_entity_del.TabIndex = 8;
|
||||
btn_entity_del.Text = "Del";
|
||||
btn_entity_del.UseVisualStyleBackColor = true;
|
||||
btn_entity_del.Click += btn_entity_del_Click;
|
||||
//
|
||||
// btn_entity_add
|
||||
//
|
||||
btn_entity_add.Enabled = false;
|
||||
btn_entity_add.Location = new Point(313, 12);
|
||||
btn_entity_add.Name = "btn_entity_add";
|
||||
btn_entity_add.Size = new Size(46, 29);
|
||||
btn_entity_add.TabIndex = 7;
|
||||
btn_entity_add.Text = "Add";
|
||||
btn_entity_add.UseVisualStyleBackColor = true;
|
||||
btn_entity_add.Click += btn_entity_add_Click;
|
||||
//
|
||||
// btn_import_entityData
|
||||
//
|
||||
btn_import_entityData.Enabled = false;
|
||||
btn_import_entityData.Location = new Point(493, 12);
|
||||
btn_import_entityData.Name = "btn_import_entityData";
|
||||
btn_import_entityData.Size = new Size(70, 29);
|
||||
btn_import_entityData.TabIndex = 9;
|
||||
btn_import_entityData.Text = "Import";
|
||||
btn_import_entityData.UseVisualStyleBackColor = true;
|
||||
btn_import_entityData.Click += btn_import_entityData_Click;
|
||||
//
|
||||
// btn_export_entityData
|
||||
//
|
||||
btn_export_entityData.Enabled = false;
|
||||
btn_export_entityData.Location = new Point(569, 12);
|
||||
btn_export_entityData.Name = "btn_export_entityData";
|
||||
btn_export_entityData.Size = new Size(66, 29);
|
||||
btn_export_entityData.TabIndex = 10;
|
||||
btn_export_entityData.Text = "Export";
|
||||
btn_export_entityData.UseVisualStyleBackColor = true;
|
||||
btn_export_entityData.Click += btn_export_entityData_Click;
|
||||
//
|
||||
// statusTextbox
|
||||
//
|
||||
statusTextbox.BorderStyle = BorderStyle.FixedSingle;
|
||||
statusTextbox.ImeMode = ImeMode.NoControl;
|
||||
statusTextbox.Location = new Point(660, 14);
|
||||
statusTextbox.Name = "statusTextbox";
|
||||
statusTextbox.ReadOnly = true;
|
||||
statusTextbox.Size = new Size(496, 27);
|
||||
statusTextbox.TabIndex = 11;
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(8F, 20F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(1176, 881);
|
||||
Controls.Add(statusTextbox);
|
||||
Controls.Add(btn_export_entityData);
|
||||
Controls.Add(btn_import_entityData);
|
||||
Controls.Add(btn_entity_del);
|
||||
Controls.Add(btn_entity_add);
|
||||
Controls.Add(entityTreeView);
|
||||
Controls.Add(dirTreeView);
|
||||
Controls.Add(btn_save);
|
||||
Controls.Add(btn_load);
|
||||
Name = "Form1";
|
||||
Text = "BetterNPC Editor V1.0";
|
||||
Load += Form1_Load;
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Button btn_load;
|
||||
private Button btn_save;
|
||||
private TreeView dirTreeView;
|
||||
private TreeView entityTreeView;
|
||||
private Button btn_entity_del;
|
||||
private Button btn_entity_add;
|
||||
private Button btn_import_entityData;
|
||||
private Button btn_export_entityData;
|
||||
private TextBox statusTextbox;
|
||||
}
|
||||
}
|
716
Better NCP Editor/Form1.cs
Normal file
716
Better NCP Editor/Form1.cs
Normal file
@ -0,0 +1,716 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Better_NCP_Editor
|
||||
{
|
||||
public partial class Form1 : Form
|
||||
{
|
||||
private string currentJsonFilePath;
|
||||
private JsonNode currentJson;
|
||||
private bool fileModified = false;
|
||||
private ToolTips toolTips = new ToolTips();
|
||||
|
||||
// In your Form1 class:
|
||||
private ToolTip customToolTip = new ToolTip();
|
||||
private TreeNode lastHoveredNode = null;
|
||||
|
||||
public Form1()
|
||||
{
|
||||
InitializeComponent();
|
||||
dirTreeView.AfterSelect += DirTreeView_AfterSelect;
|
||||
entityTreeView.NodeMouseDoubleClick += entityTreeView_NodeMouseDoubleClick;
|
||||
entityTreeView.AfterSelect += entityTreeView_AfterSelect;
|
||||
|
||||
// Prevent the form from shrinking below its current size.
|
||||
this.MinimumSize = new Size(1045, 700); // Set minimum allowed size
|
||||
this.FormBorderStyle = FormBorderStyle.Sizable;
|
||||
|
||||
// Example layout using docking:
|
||||
dirTreeView.Dock = DockStyle.Left;
|
||||
entityTreeView.Dock = DockStyle.Fill;
|
||||
|
||||
// Optionally, set a fixed width for the dirTreeView:
|
||||
dirTreeView.Width = 290;
|
||||
|
||||
// In the designer or constructor:
|
||||
dirTreeView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left;
|
||||
entityTreeView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
entityTreeView.ShowNodeToolTips = false;
|
||||
|
||||
// Configure the custom tooltip.
|
||||
customToolTip.AutoPopDelay = 15000; // Show for 15 seconds.
|
||||
customToolTip.AutomaticDelay = 1000;
|
||||
customToolTip.ShowAlways = true;
|
||||
customToolTip.InitialDelay = 500;
|
||||
customToolTip.ReshowDelay = 500;
|
||||
customToolTip.UseAnimation = true;
|
||||
|
||||
// Subscribe to events.
|
||||
entityTreeView.MouseMove += EntityTreeView_MouseMove;
|
||||
entityTreeView.MouseLeave += EntityTreeView_MouseLeave;
|
||||
|
||||
|
||||
}
|
||||
private void EntityTreeView_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
int xOffset = 15;
|
||||
int yOffset = 0;
|
||||
TreeNode node = entityTreeView.GetNodeAt(e.Location);
|
||||
if (node != null)
|
||||
{
|
||||
// Measure the text size for the node using the TreeView's font.
|
||||
Size textSize = TextRenderer.MeasureText(node.Text, entityTreeView.Font);
|
||||
// Construct a rectangle that represents the area occupied by the text.
|
||||
Rectangle textRect = new Rectangle(node.Bounds.X, node.Bounds.Y, textSize.Width, node.Bounds.Height);
|
||||
|
||||
if (textRect.Contains(e.Location))
|
||||
{
|
||||
if (node != lastHoveredNode)
|
||||
{
|
||||
lastHoveredNode = node;
|
||||
// Use an offset so the tooltip doesn't overlap the text.
|
||||
Point offsetLocation = new Point(e.Location.X + xOffset, e.Location.Y + xOffset);
|
||||
customToolTip.Show(node.ToolTipText, entityTreeView, offsetLocation, customToolTip.AutoPopDelay);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
customToolTip.Hide(entityTreeView);
|
||||
lastHoveredNode = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
customToolTip.Hide(entityTreeView);
|
||||
lastHoveredNode = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void EntityTreeView_MouseLeave(object sender, EventArgs e)
|
||||
{
|
||||
customToolTip.Hide(entityTreeView);
|
||||
lastHoveredNode = null;
|
||||
}
|
||||
|
||||
private void Form1_Load(object sender, EventArgs e)
|
||||
{
|
||||
// Other initialization code, if needed.
|
||||
}
|
||||
|
||||
private void btn_load_Click(object sender, EventArgs e)
|
||||
{
|
||||
using FolderBrowserDialog folderDialog = new();
|
||||
folderDialog.Description = "Select a directory containing JSON files";
|
||||
folderDialog.UseDescriptionForTitle = true;
|
||||
folderDialog.ShowNewFolderButton = false;
|
||||
|
||||
if (folderDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
string selectedPath = folderDialog.SelectedPath;
|
||||
dirTreeView.Nodes.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
TreeNode rootNode = new TreeNode(Path.GetFileName(selectedPath))
|
||||
{
|
||||
Tag = selectedPath
|
||||
};
|
||||
dirTreeView.Nodes.Add(rootNode);
|
||||
LoadJsonFiles(rootNode, selectedPath);
|
||||
rootNode.Expand();
|
||||
statusTextbox.Text = $"Loaded directory: {selectedPath}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Error loading directory: {ex.Message}", "Error",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadJsonFiles(TreeNode parentNode, string directoryPath)
|
||||
{
|
||||
foreach (string filePath in Directory.GetFiles(directoryPath, "*.json"))
|
||||
{
|
||||
TreeNode fileNode = new TreeNode(Path.GetFileName(filePath))
|
||||
{
|
||||
Tag = filePath
|
||||
};
|
||||
parentNode.Nodes.Add(fileNode);
|
||||
}
|
||||
|
||||
foreach (string subDirPath in Directory.GetDirectories(directoryPath))
|
||||
{
|
||||
TreeNode subDirNode = new TreeNode(Path.GetFileName(subDirPath))
|
||||
{
|
||||
Tag = subDirPath
|
||||
};
|
||||
parentNode.Nodes.Add(subDirNode);
|
||||
LoadJsonFiles(subDirNode, subDirPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void DirTreeView_AfterSelect(object sender, TreeViewEventArgs e)
|
||||
{
|
||||
if (e.Node?.Tag is string filePath &&
|
||||
Path.GetExtension(filePath).Equals(".json", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
LoadJsonFile(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadJsonFile(string jsonFilePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentJsonFilePath = jsonFilePath;
|
||||
string json = File.ReadAllText(jsonFilePath);
|
||||
currentJson = JsonNode.Parse(json);
|
||||
PopulateEntityTree(currentJson);
|
||||
fileModified = false;
|
||||
btn_save.Enabled = false; // No changes yet
|
||||
statusTextbox.Text = $"Loaded JSON file: {jsonFilePath}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Error loading JSON file: {ex.Message}", "Error",
|
||||
MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Populates the entityTreeView recursively from a JsonNode.
|
||||
private void PopulateEntityTree(JsonNode json)
|
||||
{
|
||||
entityTreeView.Nodes.Clear();
|
||||
// Use the filename of the current JSON file if available; otherwise default to "JSON".
|
||||
string fileName = string.IsNullOrEmpty(currentJsonFilePath) ? "JSON" : Path.GetFileName(currentJsonFilePath);
|
||||
TreeNode root = new TreeNode(fileName)
|
||||
{
|
||||
Tag = json
|
||||
};
|
||||
entityTreeView.Nodes.Add(root);
|
||||
PopulateTreeRecursive(json, root);
|
||||
root.ExpandAll();
|
||||
entityTreeView.TopNode = root;
|
||||
}
|
||||
|
||||
private void PopulateTreeRecursive(JsonNode node, TreeNode treeNode)
|
||||
{
|
||||
if (node is JsonObject obj)
|
||||
{
|
||||
foreach (var kvp in obj)
|
||||
{
|
||||
TreeNode child;
|
||||
if (kvp.Value is JsonValue)
|
||||
{
|
||||
child = new TreeNode($"{kvp.Key}: {kvp.Value}");
|
||||
}
|
||||
else
|
||||
{
|
||||
child = new TreeNode(kvp.Key);
|
||||
}
|
||||
child.ToolTipText = toolTips.tips.ContainsKey(kvp.Key) ? toolTips.tips[kvp.Key] : "";
|
||||
child.Tag = kvp.Value;
|
||||
treeNode.Nodes.Add(child);
|
||||
PopulateTreeRecursive(kvp.Value, child);
|
||||
}
|
||||
}
|
||||
else if (node is JsonArray arr)
|
||||
{
|
||||
for (int i = 0; i < arr.Count; i++)
|
||||
{
|
||||
JsonNode item = arr[i];
|
||||
TreeNode child;
|
||||
if (item is JsonValue)
|
||||
{
|
||||
child = new TreeNode($"[{i}]: {item}");
|
||||
}
|
||||
else
|
||||
{
|
||||
child = new TreeNode($"[{i}]");
|
||||
}
|
||||
child.Tag = item;
|
||||
treeNode.Nodes.Add(child);
|
||||
PopulateTreeRecursive(item, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void entityTreeView_AfterSelect(object sender, TreeViewEventArgs e)
|
||||
{
|
||||
if (e.Node != null)
|
||||
{
|
||||
bool enableAddDelButtons = false;
|
||||
bool enableImportExportButtons = false;
|
||||
|
||||
// Enable if the selected node's Tag is a JsonArray
|
||||
if (e.Node.Tag is JsonArray)
|
||||
{
|
||||
//enableButtons = true;
|
||||
enableImportExportButtons = true;
|
||||
}
|
||||
// Or if the selected node is an item in an array (its parent is a JsonArray)
|
||||
else if (e.Node.Parent != null && e.Node.Parent.Tag is JsonArray)
|
||||
{
|
||||
enableAddDelButtons = true;
|
||||
enableImportExportButtons = true;
|
||||
}
|
||||
// You can also add additional conditions for a "preset" node, e.g. by checking text:
|
||||
// else if (e.Node.Text.StartsWith("Preset", StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// enableButtons = true;
|
||||
// }
|
||||
|
||||
btn_entity_add.Enabled = enableAddDelButtons;
|
||||
btn_entity_del.Enabled = enableAddDelButtons;
|
||||
btn_export_entityData.Enabled = enableImportExportButtons;
|
||||
btn_import_entityData.Enabled = enableImportExportButtons;
|
||||
}
|
||||
else
|
||||
{
|
||||
btn_entity_add.Enabled = false;
|
||||
btn_entity_del.Enabled = false;
|
||||
btn_export_entityData.Enabled = false;
|
||||
btn_import_entityData.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// When a tree node is double-clicked, open an edit window for that value.
|
||||
private void entityTreeView_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
|
||||
{
|
||||
if (e.Node == null)
|
||||
return;
|
||||
|
||||
// Expect node text in the form "Property: Value"
|
||||
string[] parts = e.Node.Text.Split(new[] { ':' }, 2);
|
||||
if (parts.Length != 2)
|
||||
return;
|
||||
|
||||
string propName = parts[0].Trim();
|
||||
string currentVal = parts[1].Trim();
|
||||
|
||||
// Use simple heuristics to determine the type.
|
||||
Type valueType = typeof(string);
|
||||
if (bool.TryParse(currentVal, out bool b))
|
||||
valueType = typeof(bool);
|
||||
else if (int.TryParse(currentVal, out int i))
|
||||
valueType = typeof(int);
|
||||
|
||||
using (EditValueForm editForm = new EditValueForm(propName, currentVal, valueType))
|
||||
{
|
||||
if (editForm.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
object newVal = editForm.NewValue;
|
||||
string displayVal = newVal is bool ? newVal.ToString().ToLower() : newVal.ToString();
|
||||
e.Node.Text = $"{propName}: {displayVal}";
|
||||
|
||||
if (e.Node.Tag is JsonNode node)
|
||||
{
|
||||
JsonNode newNode = JsonValue.Create(newVal);
|
||||
node.ReplaceWith(newNode);
|
||||
e.Node.Tag = newNode;
|
||||
}
|
||||
// Mark the file as modified.
|
||||
fileModified = true;
|
||||
btn_save.Enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void btn_save_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(currentJsonFilePath) || currentJson == null)
|
||||
{
|
||||
MessageBox.Show("No JSON file loaded.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
string newJson = currentJson.ToJsonString(options);
|
||||
File.WriteAllText(currentJsonFilePath, newJson);
|
||||
statusTextbox.Text = "JSON file saved successfully.";
|
||||
//MessageBox.Show("JSON file saved successfully.", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
|
||||
// Reset the modified flag.
|
||||
fileModified = false;
|
||||
btn_save.Enabled = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Error saving JSON file: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private TreeNode FindNodeByFullPath(TreeNodeCollection nodes, string fullPath)
|
||||
{
|
||||
foreach (TreeNode node in nodes)
|
||||
{
|
||||
if (node.FullPath == fullPath)
|
||||
return node;
|
||||
TreeNode found = FindNodeByFullPath(node.Nodes, fullPath);
|
||||
if (found != null)
|
||||
return found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void btn_entity_add_Click(object sender, EventArgs e)
|
||||
{
|
||||
TreeNode selected = entityTreeView.SelectedNode;
|
||||
if (selected == null)
|
||||
{
|
||||
MessageBox.Show("Please select a node to duplicate.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
TreeNode parent = selected.Parent;
|
||||
if (parent == null)
|
||||
{
|
||||
MessageBox.Show("The selected node has no parent (cannot duplicate).", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the parent's Tag is a JsonArray.
|
||||
if (!(parent.Tag is JsonArray parentArray))
|
||||
{
|
||||
MessageBox.Show("The selected node's parent is not an array node.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Retrieve the underlying JsonNode for the selected node.
|
||||
if (!(selected.Tag is JsonNode selectedJsonNode))
|
||||
{
|
||||
MessageBox.Show("Selected node does not contain a valid JSON element.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Duplicate (deep clone) the selected JsonNode.
|
||||
JsonNode duplicate = selectedJsonNode.DeepClone();
|
||||
|
||||
// Add the duplicate to the parent's array.
|
||||
parentArray.Add(duplicate);
|
||||
|
||||
// Refresh only the parent's children.
|
||||
parent.Nodes.Clear();
|
||||
PopulateTreeRecursive((JsonNode)parent.Tag, parent);
|
||||
parent.ExpandAll(); // Expand the parent and all its child nodes
|
||||
|
||||
fileModified = true;
|
||||
btn_save.Enabled = true;
|
||||
|
||||
// Select the newly added item (last child in the parent's node collection).
|
||||
if (parent.Nodes.Count > 0)
|
||||
{
|
||||
TreeNode newSelected = parent.Nodes[parent.Nodes.Count - 1];
|
||||
entityTreeView.SelectedNode = newSelected;
|
||||
newSelected.EnsureVisible();
|
||||
entityTreeView.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
private void btn_entity_del_Click(object sender, EventArgs e)
|
||||
{
|
||||
TreeNode selected = entityTreeView.SelectedNode;
|
||||
if (selected == null)
|
||||
{
|
||||
MessageBox.Show("Please select a node to delete.", "Info", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
TreeNode parent = selected.Parent;
|
||||
if (parent == null)
|
||||
{
|
||||
MessageBox.Show("The selected node has no parent (cannot delete).", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the parent's Tag is a JsonArray.
|
||||
if (!(parent.Tag is JsonArray parentArray))
|
||||
{
|
||||
MessageBox.Show("The selected node's parent is not an array node.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the index of the selected node (assumed to be in the same order as in the array).
|
||||
int indexToRemove = selected.Index;
|
||||
if (indexToRemove < 0 || indexToRemove >= parentArray.Count)
|
||||
{
|
||||
MessageBox.Show("Selected node index is invalid.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is the last item in the array, ask for confirmation.
|
||||
if (parentArray.Count == 1)
|
||||
{
|
||||
DialogResult confirm = MessageBox.Show("Are you sure you want to delete the last item in the array?",
|
||||
"Confirm Deletion", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
|
||||
if (confirm != DialogResult.Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the element from the underlying JsonArray.
|
||||
parentArray.RemoveAt(indexToRemove);
|
||||
|
||||
// Refresh only the parent's children.
|
||||
parent.Nodes.Clear();
|
||||
PopulateTreeRecursive((JsonNode)parent.Tag, parent);
|
||||
parent.ExpandAll();
|
||||
|
||||
fileModified = true;
|
||||
btn_save.Enabled = true;
|
||||
|
||||
// Select the newly last item in the array; if none, select the parent.
|
||||
if (parent.Nodes.Count > 0)
|
||||
{
|
||||
TreeNode newSelected = parent.Nodes[parent.Nodes.Count - 1];
|
||||
entityTreeView.SelectedNode = newSelected;
|
||||
newSelected.EnsureVisible();
|
||||
entityTreeView.Focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
entityTreeView.SelectedNode = parent;
|
||||
parent.EnsureVisible();
|
||||
entityTreeView.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Compare two JsonNode structures (objects, arrays, or values) for basic structural compatibility.
|
||||
private bool CompareJsonStructure(JsonNode a, JsonNode b)
|
||||
{
|
||||
if (a == null || b == null)
|
||||
return a == b;
|
||||
|
||||
// Ensure both nodes are of the same concrete type.
|
||||
if (a.GetType() != b.GetType())
|
||||
return false;
|
||||
|
||||
if (a is JsonObject objA && b is JsonObject objB)
|
||||
{
|
||||
// They must have the same set of keys.
|
||||
if (objA.Count != objB.Count)
|
||||
return false;
|
||||
foreach (var kvp in objA)
|
||||
{
|
||||
if (!objB.ContainsKey(kvp.Key))
|
||||
return false;
|
||||
if (!CompareJsonStructure(kvp.Value, objB[kvp.Key]))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (a is JsonArray arrA && b is JsonArray arrB)
|
||||
{
|
||||
// If both arrays are empty, we consider them structurally compatible.
|
||||
if (arrA.Count == 0 && arrB.Count == 0)
|
||||
return true;
|
||||
// Otherwise, compare the structure of their first elements.
|
||||
if (arrA.Count > 0 && arrB.Count > 0)
|
||||
return CompareJsonStructure(arrA[0], arrB[0]);
|
||||
return false;
|
||||
}
|
||||
else if (a is JsonValue && b is JsonValue)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Compare the underlying value types.
|
||||
object valA = a.GetValue<object>();
|
||||
object valB = b.GetValue<object>();
|
||||
return valA?.GetType() == valB?.GetType();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void btn_import_entityData_Click(object sender, EventArgs e)
|
||||
{
|
||||
// Ensure a node is selected.
|
||||
TreeNode selected = entityTreeView.SelectedNode;
|
||||
if (selected == null)
|
||||
{
|
||||
MessageBox.Show("Please select a node for importing data.", "Import Error", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
// Open a file picker to select the import file.
|
||||
using (OpenFileDialog openDialog = new OpenFileDialog())
|
||||
{
|
||||
openDialog.Filter = "JSON Files (*.json)|*.json|All Files (*.*)|*.*";
|
||||
openDialog.Title = "Import JSON Data";
|
||||
|
||||
if (openDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
string importText = File.ReadAllText(openDialog.FileName);
|
||||
JsonNode importJson = JsonNode.Parse(importText);
|
||||
|
||||
// Determine if we are adding to an array or replacing a child.
|
||||
// If the selected node's Tag is a JsonArray, we add to it.
|
||||
// If the selected node's parent is a JsonArray, we replace the selected node.
|
||||
JsonArray parentArray = null;
|
||||
bool isAddingNewItem = false;
|
||||
if (selected.Tag is JsonArray)
|
||||
{
|
||||
parentArray = (JsonArray)selected.Tag;
|
||||
isAddingNewItem = true;
|
||||
}
|
||||
else if (selected.Parent != null && selected.Parent.Tag is JsonArray)
|
||||
{
|
||||
parentArray = (JsonArray)selected.Parent.Tag;
|
||||
isAddingNewItem = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageBox.Show("The selected node is not part of an array. Import operation cancelled.",
|
||||
"Import Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the target node for structure comparison.
|
||||
// If adding a new item, compare the structure of the array's first item (if any).
|
||||
// Otherwise, compare the selected node's structure.
|
||||
JsonNode targetStructure = null;
|
||||
if (isAddingNewItem)
|
||||
{
|
||||
if (parentArray.Count > 0)
|
||||
targetStructure = parentArray[0];
|
||||
else
|
||||
{
|
||||
// If the array is empty, we assume it's acceptable.
|
||||
targetStructure = importJson;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetStructure = (JsonNode)selected.Tag;
|
||||
}
|
||||
|
||||
if (!CompareJsonStructure(importJson, targetStructure))
|
||||
{
|
||||
MessageBox.Show("The imported JSON structure does not match the target structure.",
|
||||
"Import Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
fileModified = true;
|
||||
btn_save.Enabled = true;
|
||||
|
||||
if (isAddingNewItem)
|
||||
{
|
||||
// Add the imported JSON as a new item to the array.
|
||||
parentArray.Add(importJson);
|
||||
// Find the TreeNode corresponding to the array (selected node).
|
||||
TreeNode arrayNode = selected;
|
||||
// Refresh only this node's children.
|
||||
arrayNode.Nodes.Clear();
|
||||
PopulateTreeRecursive((JsonNode)arrayNode.Tag, arrayNode);
|
||||
arrayNode.ExpandAll();
|
||||
|
||||
// Select the newly added item (last child).
|
||||
if (arrayNode.Nodes.Count > 0)
|
||||
{
|
||||
TreeNode newNode = arrayNode.Nodes[arrayNode.Nodes.Count - 1];
|
||||
entityTreeView.SelectedNode = newNode;
|
||||
newNode.EnsureVisible();
|
||||
entityTreeView.Focus();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Replace the selected node's data with the imported JSON.
|
||||
JsonNode oldNode = (JsonNode)selected.Tag;
|
||||
oldNode.ReplaceWith(importJson);
|
||||
selected.Tag = importJson;
|
||||
|
||||
// Refresh the parent node.
|
||||
TreeNode parentNode = selected.Parent;
|
||||
parentNode.Nodes.Clear();
|
||||
PopulateTreeRecursive((JsonNode)parentNode.Tag, parentNode);
|
||||
parentNode.ExpandAll();
|
||||
|
||||
// Try to reselect the replaced item (by its index).
|
||||
int index = selected.Index;
|
||||
if (index >= 0 && index < parentNode.Nodes.Count)
|
||||
{
|
||||
TreeNode newNode = parentNode.Nodes[index];
|
||||
entityTreeView.SelectedNode = newNode;
|
||||
newNode.EnsureVisible();
|
||||
entityTreeView.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Error importing JSON file: {ex.Message}", "Import Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void btn_export_entityData_Click(object sender, EventArgs e)
|
||||
{
|
||||
// Ensure a node is selected and that it contains a JsonNode.
|
||||
if (entityTreeView.SelectedNode == null || !(entityTreeView.SelectedNode.Tag is JsonNode selectedJson))
|
||||
{
|
||||
MessageBox.Show("Please select a node with valid JSON data to export.",
|
||||
"Export Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
using (SaveFileDialog saveDialog = new SaveFileDialog())
|
||||
{
|
||||
saveDialog.Filter = "JSON Files (*.json)|*.json|All Files (*.*)|*.*";
|
||||
saveDialog.Title = "Export JSON Data";
|
||||
saveDialog.FileName = "exported.json";
|
||||
|
||||
if (saveDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
try
|
||||
{
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||
};
|
||||
|
||||
// Export the JSON subtree starting at the selected node.
|
||||
string exportJson = selectedJson.ToJsonString(options);
|
||||
File.WriteAllText(saveDialog.FileName, exportJson);
|
||||
MessageBox.Show("Export successful!", "Export", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Error exporting JSON: {ex.Message}", "Export Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
120
Better NCP Editor/Form1.resx
Normal file
120
Better NCP Editor/Form1.resx
Normal file
@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
17
Better NCP Editor/Program.cs
Normal file
17
Better NCP Editor/Program.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Better_NCP_Editor
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
// To customize application configuration such as set high DPI settings or default font,
|
||||
// see https://aka.ms/applicationconfiguration.
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.Run(new Form1());
|
||||
}
|
||||
}
|
||||
}
|
78
Better NCP Editor/ToolTips.cs
Normal file
78
Better NCP Editor/ToolTips.cs
Normal file
@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Better_NCP_Editor
|
||||
{
|
||||
class ToolTips
|
||||
{
|
||||
public Dictionary<string, string> tips { get; private set; } = new Dictionary<string, string>();
|
||||
|
||||
|
||||
public ToolTips()
|
||||
{
|
||||
// General tips
|
||||
tips.Add("Enabled? [true/false]", "Enables or disables NPC appearance on the map.");
|
||||
tips.Add("Remove other NPCs? [true/false]", "Removes standard NPCs within the monument area.");
|
||||
tips.Add("Radius", "NPC appearance radius.");
|
||||
tips.Add("Use minimum and maximum values? [true/false]", "Whether to enforce minimum/maximum limits.");
|
||||
|
||||
// Standard Monument tips
|
||||
tips.Add("The size of the monument", "Specifies the monument's length and width for random NPC placement and standard NPC removal boundaries.");
|
||||
|
||||
// Custom Monument tips
|
||||
tips.Add("Position", "Custom monument position on the map.");
|
||||
tips.Add("Rotation", "Custom monument rotation (needed for multi-map NPC placements).");
|
||||
|
||||
// NPC Preset tips
|
||||
tips.Add("Minimum numbers - Day", "Minimum NPCs for the day preset.");
|
||||
tips.Add("Maximum numbers - Day", "Maximum NPCs for the day preset.");
|
||||
tips.Add("Minimum numbers - Night", "Minimum NPCs for the night preset.");
|
||||
tips.Add("Maximum numbers - Night", "Maximum NPCs for the night preset.");
|
||||
tips.Add("Type of appearance (0 - random; 1 - own list) (not used for Road and Biome)", "Specifies NPC appearance type: 0 for random, 1 for custom list. Not used for roads.");
|
||||
tips.Add("Own list of locations (not used for Road and Biome)", "Custom list of NPC spawn locations. Ensure the list meets the maximum NPC count. Not used for roads.");
|
||||
tips.Add("Which loot table should the plugin use? (0 - default; 1 - own; 2 - AlphaLoot; 3 - CustomLoot; 4 - loot table of the Rust objects; 5 - combine the 1 and 4 methods)", "Select the NPC loot table type. Type 5 combines types 1 and 4.");
|
||||
tips.Add("Loot table from prefabs (if the loot table type is 4 or 5)", "Settings for the Rust loot table. See documentation for details.");
|
||||
tips.Add("Own loot table (if the loot table type is 1 or 5)", "Custom NPC loot table. See documentation for details.");
|
||||
|
||||
// NPC Settings tips
|
||||
tips.Add("Names", "List of NPC names (chosen randomly).");
|
||||
tips.Add("Health", "NPC hit points.");
|
||||
tips.Add("Roam Range", "Patrol distance from the spawn point.");
|
||||
tips.Add("Chase Range", "Chase distance from the spawn point.");
|
||||
tips.Add("Attack Range Multiplier", "Multiplier for the NPC's weapon range.");
|
||||
tips.Add("Sense Range", "Target detection radius.");
|
||||
tips.Add("Target Memory Duration [sec.]", "Time (in seconds) the NPC remembers a target.");
|
||||
tips.Add("Scale damage", "Damage multiplier applied by the NPC.");
|
||||
tips.Add("Aim Cone Scale", "Shooting spread (default in Rust is 2; non-negative only).");
|
||||
tips.Add("Detect the target only in the NPC's viewing vision cone? [true/false]", "If true, detection is limited to the NPC’s vision cone; false enables 360° detection.");
|
||||
tips.Add("Vision Cone", "NPC vision cone angle (20–180°). Not used if detection is 360°.");
|
||||
tips.Add("Speed", "NPC movement speed (default in Rust is 5).");
|
||||
tips.Add("Minimum time of appearance after death (not used for Events) [sec.]", "Minimum delay for NPC reappearance after death (not used for events).");
|
||||
tips.Add("Maximum time of appearance after death (not used for Events) [sec.]", "Maximum delay for NPC reappearance after death (not used for events).");
|
||||
tips.Add("Disable radio effects? [true/false]", "Toggle radio effects.");
|
||||
tips.Add("Is this a stationary NPC? [true/false]", "If true, the NPC remains stationary.");
|
||||
tips.Add("Remove a corpse after death? (it is recommended to use the true value to improve performance) [true/false]", "If true, NPC corpses are removed (recommended for performance).");
|
||||
tips.Add("Wear items", "List of NPC clothing and armor.");
|
||||
tips.Add("Belt items", "List of quick-access items (e.g., weapons, medkits, grenades).");
|
||||
tips.Add("Kits (it is recommended to use the previous 2 settings to improve performance)", "List of NPC kits (leave blank if not used).");
|
||||
|
||||
// Rust loot table tips
|
||||
tips.Add("Minimum numbers of prefabs", "Minimum number of prefabs in the loot table.");
|
||||
tips.Add("Maximum numbers of prefabs", "Maximum number of prefabs in the loot table.");
|
||||
|
||||
tips.Add("List of prefabs", "List of Rust object prefabs with full paths and drop chances.");
|
||||
|
||||
// Own loot table tips
|
||||
tips.Add("Minimum numbers of items", "Minimum number of items in the loot table.");
|
||||
tips.Add("Maximum numbers of items", "Maximum number of items in the loot table.");
|
||||
tips.Add("List of items", "List of NPC items, including blueprints and custom items.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user