Initial commit. Integrates with client, saves refs and merch.

This commit is contained in:
Tyler Hallada 2020-10-12 19:50:20 -04:00
commit 36abd362cd
24 changed files with 2006 additions and 0 deletions

95
.clang-format Normal file
View File

@ -0,0 +1,95 @@
---
AccessModifierOffset: -4
AlignAfterOpenBracket: DontAlign
AlignConsecutiveAssignments: 'false'
AlignConsecutiveDeclarations: 'false'
AlignConsecutiveMacros: 'false'
AlignEscapedNewlines: Left
AlignOperands: 'true'
AlignTrailingComments: 'true'
AllowAllArgumentsOnNextLine: 'false'
AllowAllConstructorInitializersOnNextLine: 'false'
AllowAllParametersOfDeclarationOnNextLine: 'false'
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: 'true'
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: 'true'
AlwaysBreakTemplateDeclarations: 'Yes'
BinPackArguments: 'true'
BinPackParameters: 'true'
BraceWrapping:
AfterCaseLabel: 'true'
AfterClass: 'true'
AfterControlStatement: 'false'
AfterEnum: 'true'
AfterExternBlock: 'true'
AfterFunction: 'true'
AfterNamespace: 'true'
AfterStruct: 'true'
AfterUnion: 'true'
BeforeCatch: 'false'
BeforeElse: 'false'
IndentBraces: 'false'
SplitEmptyFunction: 'false'
SplitEmptyNamespace: 'false'
SplitEmptyRecord: 'false'
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: 'false'
BreakConstructorInitializers: AfterColon
BreakInheritanceList: AfterColon
BreakStringLiterals: 'true'
ColumnLimit: 0
CompactNamespaces: 'false'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'false'
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: 'false'
DeriveLineEnding: 'true'
DerivePointerAlignment: 'false'
DisableFormat: 'false'
FixNamespaceComments: 'false'
IncludeBlocks: Preserve
IndentCaseBlocks: 'true'
IndentCaseLabels: 'false'
IndentGotoLabels: 'false'
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: 'true'
KeepEmptyLinesAtTheStartOfBlocks: 'false'
Language: Cpp
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
PointerAlignment: Left
ReflowComments : 'false'
SortIncludes: 'true'
SortUsingDeclarations: 'true'
SpaceAfterCStyleCast: 'false'
SpaceAfterLogicalNot: 'false'
SpaceAfterTemplateKeyword: 'true'
SpaceBeforeAssignmentOperators: 'true'
SpaceBeforeCpp11BracedList: 'false'
SpaceBeforeCtorInitializerColon: 'true'
SpaceBeforeInheritanceColon: 'true'
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: 'true'
SpaceBeforeSquareBrackets: 'false'
SpaceInEmptyBlock: 'false'
SpaceInEmptyParentheses: 'false'
SpacesBeforeTrailingComments: 2
SpacesInAngles: 'false'
SpacesInCStyleCastParentheses: 'false'
SpacesInConditionalStatement: 'false'
SpacesInContainerLiterals: 'true'
SpacesInParentheses: 'false'
SpacesInSquareBrackets: 'false'
Standard: c++17
TabWidth: 4
UseCRLF: 'true'
UseTab: Always
...

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

334
.gitignore vendored Normal file
View File

@ -0,0 +1,334 @@
## 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/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# 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
# 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/
# 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
*.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
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# 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
# 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
# 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
# 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 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/
# JetBrains Rider
.idea/
*.sln.iml
# 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/

41
BazaarRealmPlugin.sln Normal file
View File

@ -0,0 +1,41 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30223.230
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BazaarRealmPlugin", "BazaarRealmPlugin.vcxproj", "{27055442-2141-4A9B-ADD4-6298178CD606}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonLibSSE", "%CommonLibSSEPath%\CommonLibSSE.vcxproj", "{3E4B6EF3-EC6E-46CF-9003-EEB57A258B82}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{27055442-2141-4A9B-ADD4-6298178CD606}.Debug|x64.ActiveCfg = Debug|x64
{27055442-2141-4A9B-ADD4-6298178CD606}.Debug|x64.Build.0 = Debug|x64
{27055442-2141-4A9B-ADD4-6298178CD606}.Debug|x86.ActiveCfg = Debug|Win32
{27055442-2141-4A9B-ADD4-6298178CD606}.Debug|x86.Build.0 = Debug|Win32
{27055442-2141-4A9B-ADD4-6298178CD606}.Release|x64.ActiveCfg = Release|x64
{27055442-2141-4A9B-ADD4-6298178CD606}.Release|x64.Build.0 = Release|x64
{27055442-2141-4A9B-ADD4-6298178CD606}.Release|x86.ActiveCfg = Release|Win32
{27055442-2141-4A9B-ADD4-6298178CD606}.Release|x86.Build.0 = Release|Win32
{3E4B6EF3-EC6E-46CF-9003-EEB57A258B82}.Debug|x64.ActiveCfg = Debug|x64
{3E4B6EF3-EC6E-46CF-9003-EEB57A258B82}.Debug|x64.Build.0 = Debug|x64
{3E4B6EF3-EC6E-46CF-9003-EEB57A258B82}.Debug|x86.ActiveCfg = Debug|Win32
{3E4B6EF3-EC6E-46CF-9003-EEB57A258B82}.Debug|x86.Build.0 = Debug|Win32
{3E4B6EF3-EC6E-46CF-9003-EEB57A258B82}.Release|x64.ActiveCfg = Release|x64
{3E4B6EF3-EC6E-46CF-9003-EEB57A258B82}.Release|x64.Build.0 = Release|x64
{3E4B6EF3-EC6E-46CF-9003-EEB57A258B82}.Release|x86.ActiveCfg = Release|Win32
{3E4B6EF3-EC6E-46CF-9003-EEB57A258B82}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {85DD0553-0922-4F81-80CF-7DB29ADD49F0}
EndGlobalSection
EndGlobal

262
BazaarRealmPlugin.vcxproj Normal file
View File

@ -0,0 +1,262 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{27055442-2141-4a9b-add4-6298178cd606}</ProjectGuid>
<RootNamespace>BazaarRealm</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>BazaarRealmPlugin</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
<PreferredToolArchitecture>x64</PreferredToolArchitecture>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<VcpkgTriplet>$(VcpkgUserTriplet)-custom</VcpkgTriplet>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<VcpkgTriplet>$(VcpkgUserTriplet)-custom</VcpkgTriplet>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<VcpkgTriplet>$(VcpkgUserTriplet)-custom</VcpkgTriplet>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<VcpkgTriplet>$(VcpkgUserTriplet)-custom</VcpkgTriplet>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<EnforceTypeConversionRules>true</EnforceTypeConversionRules>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PCH.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(ProjectDir)\src;$(CommonLibSSEPath)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<ForcedIncludeFiles>PCH.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
<CompileAs>CompileAsCpp</CompileAs>
<UseFullPaths>false</UseFullPaths>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalOptions>/experimental:external /external:anglebrackets /external:W0 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
<PostBuildEvent>
<Command>copy "$(TargetPath)" "$(Skyrim64Path)\Data\SKSE\Plugins\$(TargetFileName)" /Y</Command>
<Message>Copy the compiled dll to the Skyrim directory</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<EnforceTypeConversionRules>true</EnforceTypeConversionRules>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PCH.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(ProjectDir)\src;$(CommonLibSSEPath)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<ForcedIncludeFiles>PCH.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
<CompileAs>CompileAsCpp</CompileAs>
<UseFullPaths>false</UseFullPaths>
<AdditionalOptions>/experimental:external /external:anglebrackets /external:W0 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
</Link>
<PostBuildEvent>
<Command>copy "$(TargetPath)" "$(Skyrim64Path)\Data\SKSE\Plugins\$(TargetFileName)" /Y</Command>
<Message>Copy the compiled dll to the Skyrim directory</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<EnforceTypeConversionRules>true</EnforceTypeConversionRules>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PCH.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(ProjectDir)src;$(CommonLibSSEPath)\include;$(BazaarRealmClientPath);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<ForcedIncludeFiles>PCH.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
<CompileAs>CompileAsCpp</CompileAs>
<UseFullPaths>false</UseFullPaths>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<AdditionalOptions>/experimental:external /external:anglebrackets /external:W0 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(BazaarRealmClientPath)\target\debug\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>ShopkeeperClient.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(TargetPath)" "$(Skyrim64Path)\Data\SKSE\Plugins\$(TargetFileName)" /Y</Command>
<Message>Copy the compiled dll to the Skyrim directory</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<EnforceTypeConversionRules>true</EnforceTypeConversionRules>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>PCH.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>$(ProjectDir)src;$(CommonLibSSEPath)\include;$(BazaarRealmClientPath);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<ForcedIncludeFiles>PCH.h;%(ForcedIncludeFiles)</ForcedIncludeFiles>
<CompileAs>CompileAsCpp</CompileAs>
<UseFullPaths>false</UseFullPaths>
<AdditionalOptions>/experimental:external /external:anglebrackets /external:W0 %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>DebugFull</GenerateDebugInformation>
<AdditionalLibraryDirectories>$(BazaarRealmClientPath)\target\debug\;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>ShopkeeperClient.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
<PostBuildEvent>
<Command>copy "$(TargetPath)" "$(Skyrim64Path)\Data\SKSE\Plugins\$(TargetFileName)" /Y</Command>
<Message>Copy the compiled dll to the Skyrim directory</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\BRClient.cpp" />
<ClCompile Include="src\BRInteriorRefList.cpp" />
<ClCompile Include="src\BRMerchandiseList.cpp" />
<ClCompile Include="src\BROwner.cpp" />
<ClCompile Include="src\BRShop.cpp" />
<ClCompile Include="src\main.cpp" />
<ClCompile Include="src\PCH.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
<MultiProcessorCompilation Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</MultiProcessorCompilation>
<MultiProcessorCompilation Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</MultiProcessorCompilation>
<MultiProcessorCompilation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</MultiProcessorCompilation>
<MultiProcessorCompilation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</MultiProcessorCompilation>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\BRClient.h" />
<ClInclude Include="src\BRInteriorRefList.h" />
<ClInclude Include="src\BRMerchandiseList.h" />
<ClInclude Include="src\BROwner.h" />
<ClInclude Include="src\BRShop.h" />
<ClInclude Include="src\NativeFunctions.h" />
<ClInclude Include="src\PCH.h" />
<ClInclude Include="src\version.h" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(CommonLibSSEPath)\CommonLibSSE.vcxproj">
<Project>{3e4b6ef3-ec6e-46cf-9003-eeb57a258b82}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include=".clang-format" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="src">
<UniqueIdentifier>{e487ea2f-dbf7-49c6-bd70-00519f35c76c}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\main.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="src\PCH.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="src\BRClient.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="src\BROwner.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="src\BRShop.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="src\BRInteriorRefList.cpp">
<Filter>src</Filter>
</ClCompile>
<ClCompile Include="src\BRMerchandiseList.cpp">
<Filter>src</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\PCH.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="src\version.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="src\BRClient.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="src\BROwner.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="src\BRShop.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="src\BRInteriorRefList.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="src\BRMerchandiseList.h">
<Filter>src</Filter>
</ClInclude>
<ClInclude Include="src\NativeFunctions.h">
<Filter>src</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include=".clang-format" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="version.rc" />
</ItemGroup>
</Project>

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Tyler Hallada
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

17
README.md Normal file
View File

@ -0,0 +1,17 @@
# BazaarRealmPlugin
The SKSE plugin for the Bazaar Realm Skyrim mod.
## Building
[Follow the CommonLibSSE set-up
instructions](https://github.com/Ryan-rsm-McKenzie/CommonLibSSE/wiki/Getting-Started#commonlibsse).
Instead of cloning the example plugin, clone this repo instead.
This plugin relies on a separate DLL plugin called `BazaarRealmClient`. To build
this plugin, it needs to be linked to it at compile-time. Checkout
`BazaarRealmClient` in a place of your choosing, build it, and then set the
environment variable `BazaarRealmClientPath` to the path you chose.
Building the plugin in Visual Studio will automatically place the built plugin
DLL in the `Skyrim Special Edition\Data\SKSE\Plugins\` folder.

25
src/BRClient.cpp Normal file
View File

@ -0,0 +1,25 @@
#include "bindings.h"
void Init(RE::StaticFunctionTag*)
{
logger::info("Entered Init");
init();
logger::info("Init done");
}
std::string GenerateApiKey(RE::StaticFunctionTag*)
{
logger::info("Entered GenerateApiKey");
char *api_key = generate_api_key();
logger::info(FMT_STRING("GenerateApiKey api_key: {}"), api_key);
return api_key;
}
bool StatusCheck(RE::StaticFunctionTag*, RE::BSFixedString api_url)
{
logger::info("Entered StatusCheck");
logger::info(FMT_STRING("StatusCheck api_url: {}"), api_url);
bool result = status_check(api_url.c_str());
logger::info(FMT_STRING("StatusCheck result: {}"), result ? "true" : "false");
return result;
}

5
src/BRClient.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
void Init(RE::StaticFunctionTag*);
std::string GenerateApiKey(RE::StaticFunctionTag*);
bool StatusCheck(RE::StaticFunctionTag*, RE::BSFixedString api_url);

278
src/BRInteriorRefList.cpp Normal file
View File

@ -0,0 +1,278 @@
#include "bindings.h"
#include "NativeFunctions.h"
int CreateInteriorRefList(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, uint32_t shop_id, RE::TESObjectCELL * cell)
{
logger::info("Entered CreateInteriorRefList");
if (!cell)
return -1;
RE::TESDataHandler * data_handler = RE::TESDataHandler::GetSingleton();
std::vector<RefRecord> ref_records;
for (auto entry = cell->references.begin(); entry != cell->references.end(); ++entry)
{
// TODO: skip saving the ref if it is part of mod file
// OR: continue to save it, but with a special flag (useful in order to allow repositioning default statics)
// Then, during Load, skip PlaceAtMe, lookup the ref by id and then reposition it
RE::TESObjectREFR * ref = (*entry).get();
const char * name = ref->GetName();
logger::info(FMT_STRING("CreateInteriorRefList ref: {}"), name);
const RE::TESBoundObject * base = ref->GetBaseObject();
if (base) {
RE::FormID base_form_id = base->GetFormID();
if (base_form_id == 7) {
// skip saving player ref
continue;
}
const char * form_name = base->GetName();
const RE::FormType form_type = base->GetFormType();
logger::info(FMT_STRING("CreateInteriorRefList form: {} ({:x})"), form_name, (uint32_t)form_type);
float position_x = ref->GetPositionX();
float position_y = ref->GetPositionY();
float position_z = ref->GetPositionZ();
float angle_x = ref->GetAngleX();
float angle_y = ref->GetAngleY();
float angle_z = ref->GetAngleZ();
RE::NiNPShortPoint3 boundMin = base->boundData.boundMin;
RE::NiNPShortPoint3 boundMax = base->boundData.boundMax;
uint16_t bound_x = boundMax.x > boundMin.x ? boundMax.x - boundMin.x : boundMin.x - boundMax.x;
uint16_t bound_y = boundMax.y > boundMin.y ? boundMax.y - boundMin.y : boundMin.y - boundMax.y;
uint16_t bound_z = boundMax.z > boundMin.z ? boundMax.z - boundMin.z : boundMin.z - boundMax.z;
logger::info(FMT_STRING("CreateInteriorRefList bounds: width: {:d}, length: {:d}, height: {:d}"), bound_x, bound_y, bound_z);
uint16_t scale = ref->refScale;
logger::info(FMT_STRING("CreateInteriorRefList position: {:.2f}, {:.2f}, {:.2f} angle: {:.2f}, {:.2f}, {:.2f} scale: {:d}"), position_x, position_y, position_z, angle_x, angle_y, angle_z, scale);
logger::info(FMT_STRING("CreateInteriorRefList deleted: {:d}, wants delete: {:d}"), ref->IsMarkedForDeletion(), ref->inGameFormFlags.all(RE::TESObjectREFR::InGameFormFlag::kWantsDelete));
RE::TESFile * base_file = base->GetFile(0);
char * base_file_name = base_file->fileName;
bool is_light = base_file->recordFlags.all(RE::TESFile::RecordFlag::kSmallFile);
uint32_t base_local_form_id = is_light ? base_form_id & 0xfff : base_form_id & 0xFFFFFF;
//_MESSAGE("FILE: %s isLight: %d formID: 0x%x localFormId: 0x%x", file_name, is_light, form_id, local_form_id);
RE::FormID ref_form_id = ref->GetFormID();
uint16_t ref_mod_index = ref_form_id >> 24;
char * ref_file_name = nullptr;
uint32_t ref_local_form_id = ref_form_id & 0xFFFFFF;
if (ref_mod_index != 255) { // not a temp ref
if (ref_mod_index == 254) { // is a light mod
ref_local_form_id = ref_form_id & 0xfff;
ref_mod_index = (ref_form_id >> 12) & 0xfff;
const RE::TESFile * ref_file = data_handler->LookupLoadedLightModByIndex(ref_mod_index);
ref_file_name = _strdup(ref_file->fileName);
}
else {
const RE::TESFile * ref_file = data_handler->LookupLoadedModByIndex(ref_mod_index);
ref_file_name = _strdup(ref_file->fileName);
}
}
ref_records.push_back({
base_file_name,
base_local_form_id,
ref_file_name,
ref_local_form_id,
position_x,
position_y,
position_z,
angle_x,
angle_y,
angle_z,
scale,
});
}
}
int interior_ref_list_id = create_interior_ref_list(api_url.c_str(), api_key.c_str(), shop_id, &ref_records[0], ref_records.size());
logger::info(FMT_STRING("CreateInteriorRefList result: {}"), interior_ref_list_id);
return interior_ref_list_id;
}
bool ClearCell(RE::StaticFunctionTag*, RE::TESObjectCELL* cell_ignored)
{
logger::info("Entered ClearCell");
RE::TESDataHandler * data_handler = RE::TESDataHandler::GetSingleton();
using func_t = bool(RE::TESObjectREFR* a_thisObj, void* a_param1, void* a_param2, double& a_result);
RE::TESObjectCELL * cell = RE::TESObjectCELL::LookupByEditorID<RE::TESObjectCELL>("BREmpty");
logger::info(FMT_STRING("ClearCell lookup cell override name: {} id: {:x}"), cell->GetName(), (uint32_t)cell->GetFormID());
if (!cell) {
logger::error("ClearCell cell is null!");
return false;
}
// Destroy existing references
for (auto entry = cell->references.begin(); entry != cell->references.end(); ++entry)
{
RE::TESObjectREFR * ref = (*entry).get();
RE::FormID form_id = ref->GetFormID();
logger::info(FMT_STRING("ClearCell ref form_id: {:x}"), (uint32_t)form_id);
int mod_index = form_id >> 24;
if (mod_index != 255) {
RE::TESFile * file = ref->GetDescriptionOwnerFile();
if (file) {
bool is_light = file->recordFlags.all(RE::TESFile::RecordFlag::kSmallFile);
uint32_t local_form_id = is_light ? ref->GetFormID() & 0xfff : form_id & 0xFFFFFF;
if (!data_handler->LookupForm<RE::TESObjectREFR>(local_form_id, file->fileName)) {
logger::info(FMT_STRING("ClearCell ref was not in mod file! {:x} {}"), local_form_id, ref->GetName());
}
else {
logger::info(FMT_STRING("ClearCell ref in mod file {:x} {}"), local_form_id, ref->GetName());
}
} else {
logger::info(FMT_STRING("ClearCell ref not in ANY file! {:x} {}"), (uint32_t)form_id, ref->GetName());
}
}
else {
logger::info(FMT_STRING("ClearCell ref is a temp ref, deleting {:x} {}"), (uint32_t)form_id, ref->GetName());
ref->Disable(); // disabling first is required to prevent CTD on unloading cell
ref->SetDelete(true);
}
}
return true;
}
// TODO: store the FFIResult in a HashMap cache (with ids assigned from incrementing a global int) and return its key in this function
// * bool BRResult.IsOk(int resultId) ; if false, then only GetErr() shall be accessed
// * bool BRResult.IsErr(int resultId) ; maybe not needed
// * string BRResult.GetErr(int resultId) ; always a string
// * string BRResult.GetType(int resultId) ; returns type of Ok value encoded as string
// * BRResult.Drop(int resultId) ; once we are done using the data from the result or displaying an error, delete it from the cache and free the memory
// These will panic if a) the result is an error or b) the type of the result value different than what was requested
// That will cause CTDs but there's not much else to do, silently causing undefined behavior would be worse
// Every step of this API has an explicit check you must make before continuing. If you break that contract, that's on you, the scripter.
// * string BRResult.GetString(int resultId)
// * int BRResult.GetInt(int resultId)
// * bool BRResult.GetBool(int resultId)
// * int BRResult.GetResponseId(int resultId)
// For BRResponse (maybe just completely get rid of this):
// * int BRResponse.GetStatus(int responseId) ; if >= 300 then none of the other functions should be accessed
// * bool BRResponse.IsFromCache(int responseId)
// * string BRResponse.GetType(int responseId) ; may return things like "string" or "shop"
// * string BRResponse.GetString(int responseId) ; is this going to be needed?
// * int BRResponse.GetShopId(int responseId) ; for a get_shop request
// * int BRResponse.GetInteriorRefListId(int responseId) ; for a get_interior_ref_list request
// * BRResponse.Drop()
// The following will return values from a HashMap cache on the rust-side where entries are not evicted unless explicitly told to by papyrus.
// These methods will panic if there isn't an entry in the cache for the id. This can happen if a request was not made beforehand and verified to be non-err result with a non-error response.
// For BRShop
// * string BRShop.GetName(int shopId)
// * int BRShop.GetOwnerId(int shopId)
// * BRShop.Drop()
// For BROwner
// * string BROwner.GetName(int ownerId)
// * BROwner.Drop()
// Should split this up into GetInteriorRefList and LoadInteriorRefList.
// Get: makes the request to the api and stores the Result in a rust cache, returns id of Result
// Load: given id of interior ref list, gets the data from the rust cache, does the PlaceAtMe loop and returns an id of another Result
bool LoadInteriorRefList(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, uint32_t interior_ref_list_id, RE::TESObjectCELL* cell_ignored, RE::TESObjectREFR* target_ref)
{
logger::info("Entered LoadInteriorRefList");
RE::TESDataHandler * data_handler = RE::TESDataHandler::GetSingleton();
RE::BSScript::Internal::VirtualMachine * a_vm = RE::BSScript::Internal::VirtualMachine::GetSingleton();
using func_t = decltype(&PlaceAtMe);
REL::Relocation<func_t> PlaceAtMe_Native{ REL::ID(55672) };
using func_t2 = decltype(&MoveTo);
REL::Relocation<func_t2> MoveTo_Native(RE::Offset::TESObjectREFR::MoveTo);
// testing returning a script object
// RE::BSTSmartPointer<RE::BSScript::Object> ret = RE::BSTSmartPointer<RE::BSScript::Object>::BSTSmartPointer();
// a_vm->CreateObject(RE::BSFixedString("BRResult"), ret);
// RE::BSScript::Variable var = RE::BSScript::Variable::Variable(RE::BSScript::TypeInfo::TypeInfo(RE::BSScript::TypeInfo::RawType::kBool));
// var.SetBool(false);
// a_vm->SetPropertyValue(ret, "value", var);
// RE::BSScript::Variable ret_var = RE::BSScript::Variable::Variable(RE::BSScript::TypeInfo::TypeInfo(RE::BSScript::TypeInfo::RawType::kObject));
// ret_var.SetObject(ret);
// return ret_var;
RE::TESObjectCELL * cell = RE::TESObjectCELL::LookupByEditorID<RE::TESObjectCELL>("BREmpty");
logger::info(FMT_STRING("LoadInteriorRefList lookup cell override name: {} id: {:x}"), cell->GetName(), (uint32_t)cell->GetFormID());
if (!cell) {
logger::error("LoadInteriorRefList cell is null!");
}
//RE::TESObjectREFR * x_marker = data_handler->LookupForm<RE::TESObjectREFR>(6628, "BazaarRealm.esp");
if (target_ref) {
FFIResult<RefRecordVec> result = get_interior_ref_list(api_url.c_str(), api_key.c_str(), interior_ref_list_id);
if (result.IsOk()) {
logger::info("LoadInteriorRefList get_interior_ref_list result: OK");
RefRecordVec vec = result.AsOk();
logger::info(FMT_STRING("LoadInteriorRefList vec len: {:d}, cap: {:d}"), vec.len, vec.cap);
for (int i = 0; i < vec.len; i++) {
RefRecord ref = vec.ptr[i];
logger::info(FMT_STRING("LoadInteriorRefList ref base_mod_name: {}, base_local_form_id: {:x}"), ref.base_mod_name, ref.base_local_form_id);
logger::info(FMT_STRING("LoadInteriorRefList ref position {:.2f} {:.2f} {:.2f}, angle: {:.2f} {:.2f} {:.2f}, scale: {:d}"), ref.position_x, ref.position_y, ref.position_z, ref.angle_x, ref.angle_y, ref.angle_z, ref.scale);
if (ref.base_local_form_id == 7) {
logger::info("LoadInteriorRefList skipping player ref");
continue;
}
RE::NiPoint3 position = RE::NiPoint3::NiPoint3(ref.position_x, ref.position_y, ref.position_z);
RE::NiPoint3 angle = RE::NiPoint3::NiPoint3(ref.angle_x, ref.angle_y, ref.angle_z);
RE::TESObjectREFR * game_ref = nullptr;
if (ref.ref_mod_name) { // non-temp ref, try to lookup and reposition
game_ref = data_handler->LookupForm<RE::TESObjectREFR>(ref.ref_local_form_id, ref.ref_mod_name);
if (game_ref) {
logger::info(FMT_STRING("LoadInteriorRefList lookup ref name: {}, form_id: {:x}"), game_ref->GetName(), (uint32_t)game_ref->GetFormID());
// Failed experiment at trying to call the ObjectReference.Enable() papyrus native function
//RE::BSTSmartPointer<RE::BSScript::Object> object;
//RE::BSTSmartPointer<RE::BSScript::IStackCallbackFunctor> callback = RE::BSTSmartPointer<RE::BSScript::IStackCallbackFunctor>::BSTSmartPointer(RE::BSScript::IStackCallbackFunctor());
//a_vm->CreateObject(RE::BSFixedString("ObjectReference"), game_ref, object);
//_MESSAGE("Created Object isConstructed: %d, isInitialized: %d, isValid: %d, type: %s", object->IsConstructed(), object->IsInitialized(), object->IsValid(), object->GetTypeInfo()->GetName());
//RE::BSTHashMap<RE::VMStackID, RE::BSTSmartPointer<RE::BSScript::Stack>> runningStacks = a_vm->allRunningStacks;
//_MESSAGE("allRunningStacks size: %d", runningStacks.size());
//for (auto entry = runningStacks.begin(); entry != runningStacks.end(); ++entry) {
//_MESSAGE("allRunningStacks %d", entry->first);
//}
//a_vm->DispatchMethodCall(object, RE::BSFixedString("Enable"), RE::MakeFunctionArguments<>(), RE::BSScript::IStackCallbackFunctor::IStackCallbackFunctor())
}
else {
logger::info(FMT_STRING("LoadInteriorRefList lookup ref not found, ref_mod_name: {}, ref_local_form_id: {:x}"), ref.ref_mod_name, ref.ref_local_form_id);
}
}
if (!game_ref) { // temporary reference or non-temp ref not found, recreate from base form
RE::TESForm * form = data_handler->LookupForm(ref.base_local_form_id, ref.base_mod_name);
if (!form) { // form is not found, might be in an uninstalled mod
logger::warn(FMT_STRING("LoadInteriorRefList not spawning ref for form that could not be found in installed mods, base_mod_name: {}, base_local_form_id: {:d}"), ref.base_mod_name, ref.base_local_form_id);
continue;
}
logger::info(FMT_STRING("LoadInteriorRefList lookup form name: {}, form_id: {:x}"), form->GetName(), (uint32_t)form->GetFormID());
game_ref = PlaceAtMe_Native(a_vm, 0, target_ref, form, 1, false, false);
}
MoveTo_Native(game_ref, game_ref->CreateRefHandle(), cell, cell->worldSpace, position, angle);
game_ref->data.angle = angle; // set angle directly to fix bug with MoveTo in an unloaded target cell
}
}
else {
const char * error = result.AsErr();
logger::error(FMT_STRING("LoadInteriorRefList get_interior_ref_list error: {}"), error);
return false;
}
}
else {
logger::error("LoadInteriorRefList target_ref is null!");
return false;
}
//RE::TESForm * gold = RE::TESForm::LookupByID(15);
//_MESSAGE("Gold form name: %s", gold->GetName());
//_MESSAGE("Gold form id: %d", gold->GetFormID());
//using func_t = decltype(&PlaceAtMe);
//REL::Offset<func_t> func{ REL::ID(55672) };
//RE::TESObjectREFR * new_ref = func(RE::BSScript::Internal::VirtualMachine::GetSingleton(), 1, player, gold, 50, false, false);
//_MESSAGE("New ref initially disabled: %d", new_ref->IsDisabled());
//_MESSAGE("New ref persistent: %d", new_ref->loadedData->flags & RE::TESObjectREFR::RecordFlags::kPersistent);
return true;
}

5
src/BRInteriorRefList.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
int CreateInteriorRefList(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, uint32_t shop_id, RE::TESObjectCELL * cell);
bool ClearCell(RE::StaticFunctionTag*, RE::TESObjectCELL* cell_ignored);
bool LoadInteriorRefList(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, uint32_t interior_ref_list_id, RE::TESObjectCELL* cell_ignored, RE::TESObjectREFR* target_ref);

624
src/BRMerchandiseList.cpp Normal file
View File

@ -0,0 +1,624 @@
#include "bindings.h"
#include "NativeFunctions.h"
// TODO: replace "placeholder" with "buy_activator" and "ref" with "item_ref"
bool ClearMerchandiseImpl(RE::TESObjectREFR* merchant_chest, RE::TESObjectREFR* merchant_shelf, RE::TESForm* placeholder_static, RE::BGSKeyword * shelf_keyword, RE::BGSKeyword * item_keyword)
{
logger::info("Entered ClearMerchandise");
if (merchant_chest && merchant_shelf) {
RE::TESObjectCELL * cell = merchant_shelf->GetParentCell();
RE::FormID placeholder_form_id = placeholder_static->GetFormID();
RE::FormID shelf_form_id = merchant_shelf->GetFormID();
for (auto entry = cell->references.begin(); entry != cell->references.end();)
{
RE::TESObjectREFR * ref = (*entry).get();
logger::info(FMT_STRING("ClearMerchandise ref form_id: {:x}, disabled: {:d}, marked for deletion: {:d}, deleted: {:d}"), (uint32_t)ref->GetFormID(), ref->IsDisabled(), ref->IsMarkedForDeletion(), ref->IsDeleted());
RE::TESBoundObject * base = ref->GetBaseObject();
if (base) {
RE::FormID form_id = base->GetFormID();
if (form_id == placeholder_form_id) {
logger::info("ClearMerchandise found placeholder ref");
RE::TESObjectREFR * shelf_linked_ref = ref->GetLinkedRef(shelf_keyword);
if (shelf_linked_ref && shelf_linked_ref->GetFormID() == shelf_form_id) {
logger::info("ClearMerchandise placeholder ref is linked with cleared shelf");
RE::TESObjectREFR * linked_ref = ref->GetLinkedRef(item_keyword);
if (linked_ref) {
logger::info(FMT_STRING("ClearMerchandise deleting ref linked to placeholder ref: {:x}"), (uint32_t)linked_ref->GetFormID());
// TODO: should I use the MemoryManager to free these references?
// linked_ref->Load3D(false);
// linked_ref->SetPosition(linked_ref->GetPosition() -= RE::NiPoint3(-10000, -10000, -10000));
// linked_ref->Update3DPosition(false);
// linked_ref->Set3D(ref->GetCurrent3D());
// linked_ref->Release3DRelatedData();
linked_ref->Disable(); // disabling first is required to prevent CTD on unloading cell
linked_ref->SetDelete(true);
// linked_ref->DeleteThis(); // does this do anything?
}
logger::info(FMT_STRING("ClearMerchandise deleting existing placeholder ref: {:x}"), (uint32_t)ref->GetFormID());
// ref->Load3D(false);
// ref->SetPosition(ref->GetPosition() -= RE::NiPoint3(-10000, -10000, -10000));
// ref->Update3DPosition(false);
// ref->Release3DRelatedData();
ref->Disable(); // disabling first is required to prevent CTD on unloading cell
ref->SetDelete(true);
// ref->DeleteThis(); // does this do anything?
cell->references.erase(*entry++); // prevents slowdowns after many runs of ClearMerchandise
}
else {
++entry;
}
}
else {
++entry;
}
}
else {
if (ref->IsDisabled() && ref->IsMarkedForDeletion() && ref->IsDeleted()) {
logger::info("ClearMerchandise ref is probably an item from old LoadMerchandise, clearing from cell now");
cell->references.erase(*entry++);
}
else {
logger::info("ClearMerchandise ref has no base, skipping");
++entry;
}
}
}
} else {
logger::error("ClearMerchandise merchant_chest or merchant_shelf is null!");
return false;
}
return true;
}
bool LoadMerchandiseImpl(
RE::BSFixedString api_url,
RE::BSFixedString api_key,
uint32_t merchandise_list_id,
RE::TESObjectREFR* merchant_shelf,
RE::TESForm* placeholder_static,
RE::BGSKeyword* shelf_keyword,
RE::BGSKeyword* chest_keyword,
RE::BGSKeyword* item_keyword,
RE::BGSKeyword* toggle_keyword,
RE::BGSKeyword* next_keyword,
RE::BGSKeyword* prev_keyword,
int page)
{
logger::info("Entered LoadMerchandise");
logger::info(FMT_STRING("LoadMerchandise page: {:d}"), page);
RE::TESDataHandler * data_handler = RE::TESDataHandler::GetSingleton();
RE::BSScript::Internal::VirtualMachine * a_vm = RE::BSScript::Internal::VirtualMachine::GetSingleton();
using func_t = decltype(&PlaceAtMe);
REL::Relocation<func_t> PlaceAtMe_Native{ REL::ID(55672) };
using func_t2 = decltype(&MoveTo);
REL::Relocation<func_t2> MoveTo_Native(RE::Offset::TESObjectREFR::MoveTo);
REL::ID extra_linked_ref_vtbl(static_cast<std::uint64_t>(229564));
if (!merchant_shelf) {
logger::error("LoadMerchandise merchant_shelf is null!");
return false;
}
RE::TESObjectREFR * merchant_chest = merchant_shelf->GetLinkedRef(chest_keyword);
if (!merchant_chest) {
logger::error("LoadMerchandise merchant_chest is null!");
return false;
}
FFIResult<MerchRecordVec> result = get_merchandise_list(api_url.c_str(), api_key.c_str(), merchandise_list_id);
if (result.IsOk()) {
logger::info("LoadMerchandise get_merchandise_list result OK");
MerchRecordVec vec = result.AsOk();
logger::info(FMT_STRING("LoadMerchandise vec len: {:d}, cap: {:d}"), vec.len, vec.cap);
int max_page = std::ceil((float)(vec.len - 1) / (float)9);
if (vec.len > 0 && page > max_page) {
logger::info(FMT_STRING("LoadMerchandise page {:d} is greater than max_page {:d}, doing nothing"), page, max_page);
return true;
}
ClearMerchandiseImpl(merchant_chest, merchant_shelf, placeholder_static, shelf_keyword, item_keyword);
logger::info(FMT_STRING("LoadMerchandise current shelf page is: {:d}"), merchant_shelf->extraList.GetCount());
for (int i = 0; i < vec.len; i++) {
MerchRecord rec = vec.ptr[i];
logger::info(FMT_STRING("LoadMerchandise item: {:d}"), i);
if (i < (page - 1) * 9 || i >= (page - 1) * 9 + 9) {
continue;
}
RE::TESForm * form = data_handler->LookupForm(rec.local_form_id, rec.mod_name);
if (!form) { // form is not found, might be in an uninstalled mod
logger::warn(FMT_STRING("LoadMerchandise not spawning ref for form that could not be found in installed mods: {} {:d}"), rec.mod_name, rec.local_form_id);
continue;
}
logger::info(FMT_STRING("LoadMerchandise lookup form name: {}, form_id: {:x}, form_type: {:x}"), form->GetName(), (uint32_t)form->GetFormID(), (uint32_t)form->GetFormType());
RE::TESObjectREFR * ref = PlaceAtMe_Native(a_vm, 0, merchant_shelf, form, 1, false, false);
RE::TESBoundObject * base = ref->GetBaseObject();
RE::NiNPShortPoint3 boundMin = base->boundData.boundMin;
RE::NiNPShortPoint3 boundMax = base->boundData.boundMax;
uint16_t bound_x = boundMax.x > boundMin.x ? boundMax.x - boundMin.x : boundMin.x - boundMax.x;
uint16_t bound_y = boundMax.y > boundMin.y ? boundMax.y - boundMin.y : boundMin.y - boundMax.y;
uint16_t bound_z = boundMax.z > boundMin.z ? boundMax.z - boundMin.z : boundMin.z - boundMax.z;
logger::info(FMT_STRING("LoadMerchandise ref bounds width: {:d}, length: {:d}, height: {:d}"), bound_x, bound_y, bound_z);
RE::TESObjectREFR * placeholder_ref = PlaceAtMe_Native(a_vm, 0, merchant_shelf, placeholder_static, 1, false, false);
RE::NiPoint3 bound_min = ref->GetBoundMin();
RE::NiPoint3 bound_max = ref->GetBoundMax();
logger::info(FMT_STRING("LoadMerchandise ref bounds min: {:.2f} {:.2f} {:.2f}, max: {:.2f} {:.2f} {:.2f}"), bound_min.x, bound_min.y, bound_min.z, bound_max.x, bound_max.y, bound_max.z);
RE::ExtraLinkedRef * extra_linked_ref = (RE::ExtraLinkedRef*)RE::BSExtraData::Create(sizeof(RE::ExtraLinkedRef), extra_linked_ref_vtbl.address());
// RE::BGSKeywordForm * place_keyword1 = data_handler->LookupForm<RE::BGSKeywordForm>(595228, "BazaarRealm.esm");
extra_linked_ref->linkedRefs.push_back({shelf_keyword, merchant_shelf});
placeholder_ref->extraList.Add(extra_linked_ref);
// _MESSAGE("PLACEHOLDER LINKED REF: %s", placeholder_ref->GetLinkedRef(nullptr)->GetName());
// This extra count stored on the placeholder_ref indicates the quanity of the merchandise item it is linked to
RE::ExtraCount * extra_page_num = (RE::ExtraCount*)RE::BSExtraData::Create(sizeof(RE::ExtraCount), RE::Offset::ExtraCount::Vtbl.address());
extra_page_num->count = rec.quantity;
placeholder_ref->extraList.Add(extra_page_num);
float scale = 1;
int max_over_bound = 0;
if (max_over_bound < bound_x - 34) {
max_over_bound = bound_x - 34;
}
if (max_over_bound < bound_y - 34) {
max_over_bound = bound_y - 34;
}
if (max_over_bound < bound_z - 34) {
max_over_bound = bound_z - 34;
}
if (max_over_bound > 0) {
scale = ((float)34 / (float)(max_over_bound + 34)) * (float)100;
logger::info(FMT_STRING("LoadMerchandise new scale: {:.2f} {:d} (max_over_bound: {:d}"), scale, static_cast<uint16_t>(scale), max_over_bound);
ref->refScale = static_cast<uint16_t>(scale);
placeholder_ref->refScale = static_cast<uint16_t>(scale);
}
RE::NiPoint3 shelf_position = merchant_shelf->data.location;
RE::NiPoint3 shelf_angle = merchant_shelf->data.angle;
RE::NiPoint3 ref_angle = RE::NiPoint3(shelf_angle.x, shelf_angle.y, shelf_angle.z - 3.14);
RE::NiPoint3 ref_position;
int x_imbalance = (((bound_min.x * -1) - bound_max.x) * (scale / 100)) / 2;
int y_imbalance = (((bound_min.y * -1) - bound_max.y) * (scale / 100)) / 2;
// adjusts z-height so item doesn't spawn underneath it's shelf
int z_imbalance = (bound_min.z * -1) - bound_max.z;
if (z_imbalance < 0) {
z_imbalance = 0;
}
// TODO: make page size and buy_activator positions configurable per "shelf" type (where is config stored?)
if (i % 9 == 0) {
ref_position = RE::NiPoint3(shelf_position.x + 40 + x_imbalance, shelf_position.y + y_imbalance, shelf_position.z + 110 + z_imbalance);
}
else if (i % 9 == 1) {
ref_position = RE::NiPoint3(shelf_position.x + x_imbalance, shelf_position.y + y_imbalance, shelf_position.z + 110 + z_imbalance);
}
else if (i % 9 == 2) {
ref_position = RE::NiPoint3(shelf_position.x - 40 + x_imbalance, shelf_position.y + y_imbalance, shelf_position.z + 110 + z_imbalance);
}
else if (i % 9 == 3) {
ref_position = RE::NiPoint3(shelf_position.x + 40 + x_imbalance, shelf_position.y + y_imbalance, shelf_position.z + 65 + z_imbalance);
}
else if (i % 9 == 4) {
ref_position = RE::NiPoint3(shelf_position.x + x_imbalance, shelf_position.y + y_imbalance, shelf_position.z + 65 + z_imbalance);
}
else if (i % 9 == 5) {
ref_position = RE::NiPoint3(shelf_position.x - 40 + x_imbalance, shelf_position.y + y_imbalance, shelf_position.z + 65 + z_imbalance);
}
else if (i % 9 == 6) {
ref_position = RE::NiPoint3(shelf_position.x + 40 + x_imbalance, shelf_position.y + y_imbalance, shelf_position.z + 20 + z_imbalance);
}
else if (i % 9 == 7) {
ref_position = RE::NiPoint3(shelf_position.x + x_imbalance, shelf_position.y + y_imbalance, shelf_position.z + 20 + z_imbalance);
}
else if (i % 9 == 8) {
ref_position = RE::NiPoint3(shelf_position.x - 40 + x_imbalance, shelf_position.y + y_imbalance, shelf_position.z + 20 + z_imbalance);
}
RE::TESObjectCELL * cell = merchant_shelf->GetParentCell();
MoveTo_Native(ref, ref->CreateRefHandle(), cell, cell->worldSpace, ref_position - RE::NiPoint3(10000, 10000, 10000), ref_angle);
MoveTo_Native(placeholder_ref, placeholder_ref->CreateRefHandle(), cell, cell->worldSpace, ref_position, ref_angle);
// ref->Load3D(false);
// Note: passing false to this method occasionally causes the game to crash due to access violation
// RE::NiAVObject * obj_3d = ref->Load3D(true);
// None of this works, havok is still applied, which isn't that bad really
// _MESSAGE("objectReference: %x, GetBaseObject: %x", *ref->data.objectReference, *ref->GetBaseObject());
// RE::NiAVObject * cloned_obj_3d = ref->data.objectReference->Clone3D(placeholder_ref, false);
// _MESSAGE("loaded 3d: %x, cloned 3d: %x", *obj_3d, *cloned_obj_3d);
// Need to Load3D() before calling this:
// ref->SetMotionType(RE::TESObjectREFR::MotionType::kKeyframed);
// obj_3d->SetMotionType(static_cast<uint32_t>(RE::TESObjectREFR::MotionType::kKeyframed));
// obj_3d->SetMotionType(5);
// Fails if loadedData is nullptr (if Load3D is not called first):
// ref->loadedData->flags = ref->loadedData->flags | RE::TESObjectREFR::RecordFlags::kDontHavokSettle | RE::TESObjectREFR::RecordFlags::kCollisionsDisabled | RE::TESObjectREFR::RecordFlags::kCollisionsDisabled;
// ref->InitHavok();
/// ref->DetachHavok(obj_3d);
// ref->SetCollision(false);
// ref->ClampToGround();
// placeholder_ref->Load3D(false);
// RE::NiPointer<RE::NiAVObject> placeholder_3d_data = placeholder_ref->loadedData->data3D;
// _MESSAGE("PLACEHOLDER 3D (pre-set-3d): %x", placeholder_3d_data.get());
// placeholder_ref->Set3D(obj_3d); // steal the 3D model from the item ref
// RE::ExtraLight * x_light = ref->extraList.GetByType<RE::ExtraLight>();
// if (x_light) {
// _MESSAGE("ExtraLight exists on ref: %x", x_light);
// ref->extraList.RemoveByType(RE::ExtraDataType::kLight);
// x_light = ref->extraList.GetByType<RE::ExtraLight>();
// if (!x_light) {
// _MESSAGE("ExtraLight removed");
// }
// else {
// _MESSAGE("After removing ExtraLight: %x", x_light);
// }
// }
// x_light = placeholder_ref->extraList.GetByType<RE::ExtraLight>();
// if (x_light) {
// _MESSAGE("ExtraLight exists on placeholder_ref: %x", x_light);
// placeholder_ref->extraList.RemoveByType(RE::ExtraDataType::kLight);
// x_light = placeholder_ref->extraList.GetByType<RE::ExtraLight>();
// if (!x_light) {
// _MESSAGE("ExtraLight removed");
// }
// else {
// _MESSAGE("After removing ExtraLight: %x", x_light);
// }
// }
RE::BSFixedString name = RE::BSFixedString::BSFixedString(ref->GetName());
placeholder_ref->SetDisplayName(name, true);
// placeholder_ref->SetObjectReference(base);
placeholder_ref->extraList.SetOwner(base); // I'm abusing "owner" to link the activator with the Form that should be bought once activated
// Do I still need to set this flag? I could maybe use the deleted flag instead
// uint32_t phantom_ref_flag = 1 << 9; // this is my own made up ExtraFlags::Flag that marks the reference we stole the 3D from as needing to be deleted at the start of the next LoadMerchandise
// RE::ExtraFlags * x_flags = ref->extraList.GetByType<RE::ExtraFlags>();
// RE::ExtraFlags::Flag new_flags;
// if (x_flags) {
// _MESSAGE("REF XFLAGS pre-set: %x", x_flags->flags);
// new_flags = (RE::ExtraFlags::Flag)((uint32_t)(x_flags->flags) | phantom_ref_flag);
// }
// else {
// new_flags = (RE::ExtraFlags::Flag)phantom_ref_flag;
// }
//ref->extraList.SetExtraFlags(new_flags, true);
// x_flags = ref->extraList.GetByType<RE::ExtraFlags>();
extra_linked_ref->linkedRefs.push_back({item_keyword, ref});
// _MESSAGE("REF XFLAGS post-set: %x", x_flags->flags);
// Test deleting ref that owns 3d
// ref->Disable(); // disabling first is required to prevent CTD on unloading cell
// ref->SetDelete(true);
// ref->Predestroy();
// ref->formFlags |= RE::TESObjectREFR::RecordFlags::kDeleted;
// ref->AddChange(RE::TESObjectREFR::ChangeFlags::kItemExtraData);
// ref->AddChange(RE::TESObjectREFR::ChangeFlags::kGameOnlyExtra);
// ref->AddChange(RE::TESObjectREFR::ChangeFlags::kCreatedOnlyExtra);
// placeholder_ref->inGameFormFlags |= RE::TESObjectREFR::InGameFormFlag::kWantsDelete;
// placeholder_ref->AddChange(RE::TESObjectREFR::ChangeFlags::kGameOnlyExtra);
}
// I'm abusing the ExtraCount ExtraData type for storing the current page number state of the shelf
RE::ExtraCount * extra_page_num = merchant_shelf->extraList.GetByType<RE::ExtraCount>();
if (!extra_page_num) {
extra_page_num = (RE::ExtraCount*)RE::BSExtraData::Create(sizeof(RE::ExtraCount), RE::Offset::ExtraCount::Vtbl.address());
merchant_shelf->extraList.Add(extra_page_num);
}
extra_page_num->count = page;
logger::info(FMT_STRING("LoadMerchandise set shelf page to: {:d}"), merchant_shelf->extraList.GetCount());
// I'm abusing the ExtraCannotWear ExtraData type as a boolean marker which stores whether the shelf is in a loaded or cleared state
// The presense of ExtraCannotWear == loaded, its absence == cleared
// Please don't try to wear the shelves :)
RE::ExtraCannotWear * extra_is_loaded = merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>();
if (!extra_is_loaded) {
extra_is_loaded = (RE::ExtraCannotWear*)RE::BSExtraData::Create(sizeof(RE::ExtraCannotWear), RE::Offset::ExtraCannotWear::Vtbl.address());
merchant_shelf->extraList.Add(extra_is_loaded);
}
logger::info(FMT_STRING("LoadMerchandise set loaded: {:d}"), merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>() != nullptr);
RE::TESObjectREFR * toggle_ref = merchant_shelf->GetLinkedRef(toggle_keyword);
if (!toggle_ref) {
logger::error("LoadMerchandise toggle_ref is null!");
return false;
}
RE::TESObjectREFR * next_ref = merchant_shelf->GetLinkedRef(next_keyword);
if (!next_ref) {
logger::error("LoadMerchandise next_ref is null!");
return false;
}
RE::TESObjectREFR * prev_ref = merchant_shelf->GetLinkedRef(prev_keyword);
if (!prev_ref) {
logger::error("LoadMerchandise prev_ref is null!");
return false;
}
toggle_ref->SetDisplayName("Clear merchandise", true);
if (page == max_page) {
next_ref->SetDisplayName("(No next page)", true);
}
else {
next_ref->SetDisplayName(fmt::format("Advance to page %d", page + 1).c_str(), true);
}
if (page == 1) {
prev_ref->SetDisplayName("(No previous page)", true);
}
else {
prev_ref->SetDisplayName(fmt::format("Back to page %d", page - 1).c_str(), true);
}
}
else {
const char * error = result.AsErr();
logger::error(FMT_STRING("LoadMerchandise get_merchandise_list error: {}"), error);
return false;
}
return true;
}
bool ToggleMerchandise(
RE::StaticFunctionTag*,
RE::BSFixedString api_url,
RE::BSFixedString api_key,
uint32_t merchandise_list_id,
RE::TESObjectREFR* merchant_shelf,
RE::TESForm* placeholder_static,
RE::BGSKeyword* shelf_keyword,
RE::BGSKeyword* chest_keyword,
RE::BGSKeyword* item_keyword,
RE::BGSKeyword* toggle_keyword,
RE::BGSKeyword* next_keyword,
RE::BGSKeyword* prev_keyword)
{
if (!merchant_shelf) {
logger::error("ToggleMerchandise merchant_shelf is null!");
return false;
}
// I'm abusing the ExtraCannotWear ExtraData type as a boolean marker which stores whether the shelf is in a loaded or cleared state
// The presense of ExtraCannotWear == loaded, its absence == cleared
// Please don't try to wear the shelves :)
RE::ExtraCannotWear * extra_is_loaded = merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>();
if (extra_is_loaded) {
// Clear merchandise
RE::TESObjectREFR * merchant_chest = merchant_shelf->GetLinkedRef(chest_keyword);
if (!ClearMerchandiseImpl(merchant_chest, merchant_shelf, placeholder_static, shelf_keyword, item_keyword)) {
return false;
}
// Reset shelf page to 1 and set state to cleared
merchant_shelf->extraList.RemoveByType(RE::ExtraDataType::kCount);
merchant_shelf->extraList.RemoveByType(RE::ExtraDataType::kCannotWear);
RE::TESObjectREFR * toggle_ref = merchant_shelf->GetLinkedRef(toggle_keyword);
if (!toggle_ref) {
logger::error("ToggleMerchandise toggle_ref is null!");
return false;
}
RE::TESObjectREFR * next_ref = merchant_shelf->GetLinkedRef(next_keyword);
if (!next_ref) {
logger::error("ToggleMerchandise next_ref is null!");
return false;
}
RE::TESObjectREFR * prev_ref = merchant_shelf->GetLinkedRef(prev_keyword);
if (!prev_ref) {
logger::error("ToggleMerchandise prev_ref is null!");
return false;
}
toggle_ref->SetDisplayName("Load merchandise", true);
next_ref->SetDisplayName("Load merchandise", true);
prev_ref->SetDisplayName("Load merchandise", true);
return true;
}
else {
// Load merchandise
int page = merchant_shelf->extraList.GetCount();
return LoadMerchandiseImpl(api_url, api_key, merchandise_list_id, merchant_shelf, placeholder_static, shelf_keyword, chest_keyword, item_keyword, toggle_keyword, next_keyword, prev_keyword, page);
}
}
bool LoadNextMerchandise(
RE::StaticFunctionTag*,
RE::BSFixedString api_url,
RE::BSFixedString api_key,
uint32_t merchandise_list_id,
RE::TESObjectREFR* merchant_shelf,
RE::TESForm* placeholder_static,
RE::BGSKeyword* shelf_keyword,
RE::BGSKeyword* chest_keyword,
RE::BGSKeyword* item_keyword,
RE::BGSKeyword* toggle_keyword,
RE::BGSKeyword* next_keyword,
RE::BGSKeyword* prev_keyword)
{
if (!merchant_shelf) {
logger::error("LoadNextMerchandise merchant_shelf is null!");
return false;
}
int page = merchant_shelf->extraList.GetCount();
RE::ExtraCannotWear * extra_is_loaded = merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>();
if (extra_is_loaded) {
// Only advance the page if shelf is in loaded state, else just load the (first) page
page = page + 1;
}
return LoadMerchandiseImpl(api_url, api_key, merchandise_list_id, merchant_shelf, placeholder_static, shelf_keyword, chest_keyword, item_keyword, toggle_keyword, next_keyword, prev_keyword, page);
}
bool LoadPrevMerchandise(
RE::StaticFunctionTag*,
RE::BSFixedString api_url,
RE::BSFixedString api_key,
uint32_t merchandise_list_id,
RE::TESObjectREFR* merchant_shelf,
RE::TESForm* placeholder_static,
RE::BGSKeyword* shelf_keyword,
RE::BGSKeyword* chest_keyword,
RE::BGSKeyword* item_keyword,
RE::BGSKeyword* toggle_keyword,
RE::BGSKeyword* next_keyword,
RE::BGSKeyword* prev_keyword)
{
if (!merchant_shelf) {
logger::error("LoadPrevMerchandise merchant_shelf is null!");
return false;
}
int page = merchant_shelf->extraList.GetCount();
if (page == 1) { // no-op on first page
return true;
}
RE::ExtraCannotWear * extra_is_loaded = merchant_shelf->extraList.GetByType<RE::ExtraCannotWear>();
if (extra_is_loaded) {
// Only advance the page if shelf is in loaded state, else just load the (first) page
page = page - 1;
}
return LoadMerchandiseImpl(api_url, api_key, merchandise_list_id, merchant_shelf, placeholder_static, shelf_keyword, chest_keyword, item_keyword, toggle_keyword, next_keyword, prev_keyword, page);
}
bool ReplaceMerch3D(RE::StaticFunctionTag*, RE::TESObjectREFR* merchant_shelf, RE::TESForm* placeholder_static, RE::BGSKeyword* shelf_keyword, RE::BGSKeyword* item_keyword) {
logger::info("Entered ReplaceMerch3D");
if (!merchant_shelf) {
logger::error("LoadMerchandise merchant_shelf is null!");
return false;
}
RE::TESObjectCELL * cell = merchant_shelf->GetParentCell();
RE::FormID placeholder_form_id = placeholder_static->GetFormID();
RE::FormID shelf_form_id = merchant_shelf->GetFormID();
for (auto entry = cell->references.begin(); entry != cell->references.end(); ++entry)
{
RE::TESObjectREFR * ref = (*entry).get();
RE::TESBoundObject * base = ref->GetBaseObject();
if (base) {
if (base->GetFormID() == placeholder_form_id) {
logger::info(FMT_STRING("ReplaceMerch3D REF is a placeholder ref: {:x}"), (uint32_t)ref->GetFormID());
RE::TESObjectREFR * shelf_linked_ref = ref->GetLinkedRef(shelf_keyword);
if (shelf_linked_ref && shelf_linked_ref->GetFormID() == shelf_form_id) {
logger::info("ReplaceMerch3D placeholder ref is linked with loaded shelf");
RE::TESObjectREFR * linked_ref = ref->GetLinkedRef(item_keyword);
if (linked_ref) {
logger::info(FMT_STRING("ReplaceMerch3D placeholder has linked item ref: {:x}"), (uint32_t)linked_ref->GetFormID());
if (linked_ref->Is3DLoaded()) {
logger::info("ReplaceMerch3D replaceing placeholder 3D with linked item 3D");
ref->Set3D(linked_ref->GetCurrent3D());
}
else {
logger::info("ReplaceMerch3D linked item ref 3D is not loaded yet, returning false");
return false;
}
}
}
}
} else {
logger::info("ReplaceMerch3D ref has no base, skipping");
}
}
return true;
}
RE::TESForm * BuyMerchandise(RE::StaticFunctionTag*, RE::TESObjectREFR * merchandise_placeholder) {
logger::info("Entered BuyMerchandise");
logger::info(FMT_STRING("BuyMerchandise activated ref: {}"), merchandise_placeholder->GetName());
RE::TESForm * owner = merchandise_placeholder->GetOwner();
logger::info(FMT_STRING("BuyMerchandise owner: {}"), owner->GetName());
logger::info(FMT_STRING("BuyMerchandise count: {:d}"), merchandise_placeholder->extraList.GetCount());
// TODO: do add item here
// player->AddObjectToContainer(owner, *RE::ExtraDataList::ExtraDataList(), 1, merchandise_placeholder)
return owner;
}
// Return code:
// -2: No changes to save, no create request was made
// -1: Error occured
// >= 0: ID of created MerchandiseList returned by API
int CreateMerchandiseList(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, uint32_t shop_id, RE::TESObjectREFR* merchant_chest)
{
logger::info("Entered CreateMerchandiseList");
RE::TESDataHandler * data_handler = RE::TESDataHandler::GetSingleton();
std::vector<MerchRecord> merch_records;
if (!merchant_chest) {
logger::error("CreateMerchandiseList merchant_chest is null!");
return -1;
}
RE::InventoryChanges * inventory_changes = merchant_chest->GetInventoryChanges();
if (inventory_changes == nullptr) {
logger::info("CreateMerchandiseList container empty, nothing to save");
return -2;
}
RE::BSSimpleList<RE::InventoryEntryData*>* entries = inventory_changes->entryList;
int count = 0;
for (auto entry = entries->begin(); entry != entries->end(); ++entry) {
logger::info(FMT_STRING("CreateMerchandiseList container iterator count: {:d}"), count);
RE::InventoryEntryData * entry_data = *entry;
RE::TESBoundObject * base = entry_data->GetObject();
if (base) {
RE::BSSimpleList<RE::ExtraDataList*> * x_lists = entry_data->extraLists;
if (x_lists) {
const char * entry_name = entry_data->extraLists->front()->GetDisplayName(base);
logger::info(FMT_STRING("CreateMerchandiseList container item display_name: {}"), entry_name);
int x_list_count = 0;
for (auto x_list = entry_data->extraLists->begin(); x_list != entry_data->extraLists->end(); ++x_list) {
logger::info(FMT_STRING("CreateMerchandiseList container item x_list: {:d}"), x_list_count);
int x_data_count = 0;
for (auto x_data = (*x_list)->begin(); x_data != (*x_list)->end(); ++x_data) {
logger::info(FMT_STRING("CreateMerchandiseList container item x_data: {:d}, type: {:x}"), x_data_count, (uint32_t)x_data->GetType());
x_data_count++;
}
RE::ExtraEnchantment * enchantment = (RE::ExtraEnchantment*)(*x_list)->GetByType(RE::ExtraDataType::kEnchantment);
if (enchantment) {
logger::info(FMT_STRING("CreateMerchandiseList container item enchantment charge: {:d}"), enchantment->charge);
}
RE::ExtraScale * scale = (RE::ExtraScale*)(*x_list)->GetByType(RE::ExtraDataType::kScale);
if (scale) {
logger::info(FMT_STRING("CreateMerchandiseList container item scale: {:.2f}"), scale->scale);
}
x_list_count++;
}
}
const char * name = base->GetName();
uint32_t form_type = (uint32_t)base->GetFormType();
uint32_t quantity = entry_data->countDelta;
RE::FormID form_id = base->GetFormID();
logger::info(FMT_STRING("CreateMerchandiseList quantity: {:d}"), quantity);
logger::info(FMT_STRING("CreateMerchandiseList base form_id: {:x}, name: {}, type: {:x}"), (uint32_t)form_id, name, (uint32_t)form_type);
RE::TESFile * file = base->GetFile(0);
const char * mod_name = file->fileName;
bool is_light = file->recordFlags.all(RE::TESFile::RecordFlag::kSmallFile);
uint32_t local_form_id = is_light ? form_id & 0xfff : form_id & 0xFFFFFF;
//_MESSAGE("FILE: %s isLight: %d formID: 0x%x localFormId: 0x%x", file_name, is_light, form_id, local_form_id);
logger::info(FMT_STRING("CreateMerchandiseList base form file_name: {}, local_form_id"), mod_name, local_form_id);
// TODO: implement is_food
uint8_t is_food = 0;
// TODO: implement price
uint32_t price = 0;
merch_records.push_back({
mod_name,
local_form_id,
name,
quantity,
form_type,
is_food,
price,
});
}
count++;
}
int merchandise_list_id = create_merchandise_list(api_url.c_str(), api_key.c_str(), shop_id, &merch_records[0], merch_records.size());
logger::info(FMT_STRING("CreateMerchandiseList create_merchandise_list result: {:d}"), merchandise_list_id);
return merchandise_list_id;
}

45
src/BRMerchandiseList.h Normal file
View File

@ -0,0 +1,45 @@
#pragma once
bool ClearMerchandiseImpl(RE::TESObjectREFR* merchant_chest, RE::TESObjectREFR* merchant_shelf, RE::TESForm* placeholder_static, RE::BGSKeyword * shelf_keyword, RE::BGSKeyword * item_keyword);
bool ToggleMerchandise(
RE::StaticFunctionTag*,
RE::BSFixedString api_url,
RE::BSFixedString api_key,
uint32_t merchandise_list_id,
RE::TESObjectREFR* merchant_shelf,
RE::TESForm* placeholder_static,
RE::BGSKeyword* shelf_keyword,
RE::BGSKeyword* chest_keyword,
RE::BGSKeyword* item_keyword,
RE::BGSKeyword* toggle_keyword,
RE::BGSKeyword* next_keyword,
RE::BGSKeyword* prev_keyword);
bool LoadNextMerchandise(
RE::StaticFunctionTag*,
RE::BSFixedString api_url,
RE::BSFixedString api_key,
uint32_t merchandise_list_id,
RE::TESObjectREFR* merchant_shelf,
RE::TESForm* placeholder_static,
RE::BGSKeyword* shelf_keyword,
RE::BGSKeyword* chest_keyword,
RE::BGSKeyword* item_keyword,
RE::BGSKeyword* toggle_keyword,
RE::BGSKeyword* next_keyword,
RE::BGSKeyword* prev_keyword);
bool LoadPrevMerchandise(
RE::StaticFunctionTag*,
RE::BSFixedString api_url,
RE::BSFixedString api_key,
uint32_t merchandise_list_id,
RE::TESObjectREFR* merchant_shelf,
RE::TESForm* placeholder_static,
RE::BGSKeyword* shelf_keyword,
RE::BGSKeyword* chest_keyword,
RE::BGSKeyword* item_keyword,
RE::BGSKeyword* toggle_keyword,
RE::BGSKeyword* next_keyword,
RE::BGSKeyword* prev_keyword);
bool ReplaceMerch3D(RE::StaticFunctionTag*, RE::TESObjectREFR* merchant_shelf, RE::TESForm* placeholder_static, RE::BGSKeyword* shelf_keyword, RE::BGSKeyword* item_keyword);
RE::TESForm * BuyMerchandise(RE::StaticFunctionTag*, RE::TESObjectREFR * merchandise_placeholder);
int CreateMerchandiseList(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, uint32_t shop_id, RE::TESObjectREFR* merchant_chest);

13
src/BROwner.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "bindings.h"
int CreateOwner(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, RE::BSFixedString name, uint32_t mod_version)
{
logger::info("Entered CreateOwner");
logger::info(FMT_STRING("CreateOwner api_url: {}"), api_url);
logger::info(FMT_STRING("CreateOwner api_key: {}"), api_key);
logger::info(FMT_STRING("CreateOwner name: {}"), name);
logger::info(FMT_STRING("CreateOwner mod_version: {}"), mod_version);
int owner_id = create_owner(api_url.c_str(), api_key.c_str(), name.c_str(), mod_version);
logger::info(FMT_STRING("CreateOwner result: {}"), owner_id);
return owner_id;
}

3
src/BROwner.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
int CreateOwner(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, RE::BSFixedString name, uint32_t mod_version);

13
src/BRShop.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "bindings.h"
int CreateShop(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, RE::BSFixedString name, RE::BSFixedString description)
{
logger::info("Entered CreateShop");
logger::info(FMT_STRING("CreateShop api_url: {}"), api_url);
logger::info(FMT_STRING("CreateShop api_key: {}"), api_key);
logger::info(FMT_STRING("CreateShop name: {}"), name);
logger::info(FMT_STRING("CreateShop description: {}"), description);
int shop_id = create_shop(api_url.c_str(), api_key.c_str(), name.c_str(), description.c_str());
logger::info(FMT_STRING("CreateShop result: {}"), shop_id);
return shop_id;
}

3
src/BRShop.h Normal file
View File

@ -0,0 +1,3 @@
#pragma once
int CreateShop(RE::StaticFunctionTag*, RE::BSFixedString api_url, RE::BSFixedString api_key, RE::BSFixedString name, RE::BSFixedString description);

4
src/NativeFunctions.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
RE::TESObjectREFR* PlaceAtMe(RE::BSScript::IVirtualMachine* a_vm, int something, RE::TESObjectREFR* ref, RE::TESForm* form, int count, bool forcePersist, bool initiallyDisabled);
void MoveTo(RE::TESObjectREFR * ref, const RE::ObjectRefHandle& a_targetHandle, RE::TESObjectCELL* a_targetCell, RE::TESWorldSpace* a_selfWorldSpace, const RE::NiPoint3& a_position, const RE::NiPoint3& a_rotation);

1
src/PCH.cpp Normal file
View File

@ -0,0 +1 @@
#include "PCH.h"

16
src/PCH.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include "RE/Skyrim.h"
#include "SKSE/SKSE.h"
#ifdef NDEBUG
#include <spdlog/sinks/basic_file_sink.h>
#else
#include <spdlog/sinks/msvc_sink.h>
#endif
using namespace std::literals;
namespace logger = SKSE::log;
#define DLLEXPORT __declspec(dllexport)

88
src/main.cpp Normal file
View File

@ -0,0 +1,88 @@
#include "version.h"
#include "bindings.h"
#include "BRClient.h"
#include "BROwner.h"
#include "BRShop.h"
#include "BRInteriorRefList.h"
#include "BRMerchandiseList.h"
bool RegisterFuncs(RE::BSScript::IVirtualMachine* a_vm)
{
a_vm->RegisterFunction("Init", "BRClient", Init);
a_vm->RegisterFunction("StatusCheck", "BRClient", StatusCheck);
a_vm->RegisterFunction("GenerateApiKey", "BRClient", GenerateApiKey);
a_vm->RegisterFunction("Create", "BROwner", CreateOwner);
a_vm->RegisterFunction("Create", "BRShop", CreateShop);
a_vm->RegisterFunction("Create", "BRInteriorRefList", CreateInteriorRefList);
a_vm->RegisterFunction("ClearCell", "BRInteriorRefList", ClearCell);
a_vm->RegisterFunction("Load", "BRInteriorRefList", LoadInteriorRefList);
a_vm->RegisterFunction("Toggle", "BRMerchandiseList", ToggleMerchandise);
a_vm->RegisterFunction("NextPage", "BRMerchandiseList", LoadNextMerchandise);
a_vm->RegisterFunction("PrevPage", "BRMerchandiseList", LoadPrevMerchandise);
a_vm->RegisterFunction("Buy", "BRMerchandiseList", BuyMerchandise);
a_vm->RegisterFunction("Replace3D", "BRMerchandiseList", ReplaceMerch3D);
a_vm->RegisterFunction("Create", "BRMerchandiseList", CreateMerchandiseList);
return true;
}
extern "C" DLLEXPORT bool SKSEAPI SKSEPlugin_Query(const SKSE::QueryInterface* a_skse, SKSE::PluginInfo* a_info)
{
#ifndef NDEBUG
auto sink = std::make_shared<spdlog::sinks::msvc_sink_mt>();
#else
auto path = logger::log_directory();
if (!path) {
return false;
}
*path /= "BazaarRealmPlugin.log"sv;
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path->string(), true);
#endif
auto log = std::make_shared<spdlog::logger>("global log"s, std::move(sink));
#ifndef NDEBUG
log->set_level(spdlog::level::trace);
#else
log->set_level(spdlog::level::info);
log->flush_on(spdlog::level::warn);
#endif
spdlog::set_default_logger(std::move(log));
spdlog::set_pattern("%g(%#): [%^%l%$] %v"s);
logger::info(FMT_STRING("BazaarRealmPlugin v{}"), MYFP_VERSION_VERSTRING);
a_info->infoVersion = SKSE::PluginInfo::kVersion;
a_info->name = "BazaarRealmPlugin";
a_info->version = MYFP_VERSION_MAJOR;
if (a_skse->IsEditor()) {
logger::critical("Loaded in editor, marking as incompatible"sv);
return false;
}
const auto ver = a_skse->RuntimeVersion();
if (ver < SKSE::RUNTIME_1_5_39) {
logger::critical(FMT_STRING("Unsupported runtime version {}"), ver.string());
return false;
}
return true;
}
extern "C" DLLEXPORT bool SKSEAPI SKSEPlugin_Load(const SKSE::LoadInterface* a_skse)
{
logger::info("BazaarRealmPlugin loaded");
SKSE::Init(a_skse);
auto papyrus = SKSE::GetPapyrusInterface();
if (!papyrus->Register(RegisterFuncs)) {
return false;
}
return true;
}

15
src/version.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef MYFP_VERSION_INCLUDED
#define MYFP_VERSION_INCLUDED
#define MAKE_STR_HELPER(a_str) #a_str
#define MAKE_STR(a_str) MAKE_STR_HELPER(a_str)
#define MYFP_VERSION_MAJOR 1
#define MYFP_VERSION_MINOR 0
#define MYFP_VERSION_PATCH 0
#define MYFP_VERSION_BETA 0
#define MYFP_VERSION_VERSTRING \
MAKE_STR(MYFP_VERSION_MAJOR) \
"." MAKE_STR(MYFP_VERSION_MINOR) "." MAKE_STR(MYFP_VERSION_PATCH) "." MAKE_STR(MYFP_VERSION_BETA)
#endif

33
version.rc Normal file
View File

@ -0,0 +1,33 @@
#include "src\version.h"
#include <winres.h>
1 VERSIONINFO
FILEVERSION MYFP_VERSION_MAJOR, MYFP_VERSION_MINOR, MYFP_VERSION_PATCH, MYFP_VERSION_BETA
PRODUCTVERSION MYFP_VERSION_MAJOR, MYFP_VERSION_MINOR, MYFP_VERSION_PATCH, MYFP_VERSION_BETA
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "FileDescription", "MyFirstPlugin"
VALUE "FileVersion", MYFP_VERSION_VERSTRING
VALUE "InternalName", "MyFirstPlugin"
VALUE "LegalCopyright", "MIT License"
VALUE "ProductName", "MyFirstPlugin"
VALUE "ProductVersion", MYFP_VERSION_VERSTRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END