First Commit

This commit is contained in:
Matthew McConnell 2016-09-27 22:50:00 +01:00
commit cd8989f228
30 changed files with 6520 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
.hg/*
@ExileLootDrop/*
src/ExileLootDrop/bin/*
src/ExileLootDrop/obj/*
src/ExileLootDropTester/bin/*
src/ExileLootDropTester/obj/*
*.suo
src/packages/*
.hgignore

File diff suppressed because it is too large Load Diff

37
ExileLootDrop.VR/init.sqf Normal file
View File

@ -0,0 +1,37 @@
ExileServer_system_lootManager_dropItem_sqf = {
private["_lootTableName","_itemClassName","_lootTableConfig","_sum","_count","_half","_halfIndex","_lootTableEntries","_maxPossible","_chance","_startIndex","_endIndex","_i","_entry"];
_lootTableName = _this;
_itemClassName = "";
_lootTableConfig = missionConfigFile >> "CfgLootTables" >> _lootTableName;
_sum = getNumber(_lootTableConfig >> "sum");
_count = getNumber(_lootTableConfig >> "count");
_half = getNumber(_lootTableConfig >> "half");
_halfIndex = getNumber(_lootTableConfig >> "halfIndex");
_lootTableEntries = getArray(_lootTableConfig >> "items");
_maxPossible = (count _lootTableEntries)-1;
_chance = random(_sum);
if (_chance >= _half) then
{
_startIndex = _halfIndex;
_endIndex = _count - 1;
}
else
{
_startIndex = 0;
_endIndex = _halfIndex + 1;
};
if (_endIndex > _maxPossible) then
{
_endIndex = _maxPossible;
};
for "_i" from _startIndex to _endIndex do
{
_entry = _lootTableEntries select _i;
if (_chance <= (_entry select 0)) exitWith
{
_itemClassName = _entry select 1;
};
};
_itemClassName
};
ExileServer_system_lootManager_dropItem_ext = compile preprocessFileLineNumbers '\ExileLootDrop\ExileServer_system_lootManager_dropItem.sqf';

Binary file not shown.

7
LICENSE.txt Normal file
View File

@ -0,0 +1,7 @@
Exile Loot Drop
www.maca134.co.uk
© 2016 maca134
This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/.

28
README.txt Normal file
View File

@ -0,0 +1,28 @@
Exile Loot Drop
A server mod/extension to replace the Exile loot drop function with a dll.
Examples:
Get single item (returns string so is backwards compatible with Exile):
_item = 'table' call ExileServer_system_lootManager_dropItem;
Get multiple items (returns an array of items, this is good for mission stuff):
_items = ['table', 10] call ExileServer_system_lootManager_dropItem;
ExileLootDrop.VR mission contains the original Exile method for loot and the original tables for testing
// SQF
'CivillianLowerClass' call ExileServer_system_lootManager_dropItem_sqf
// DLL
'CivillianLowerClass' call ExileServer_system_lootManager_dropItem_ext
To Install:
Run the mod on the server and stick the below into CfgExileCustomCode in you mission files
```
class CfgExileCustomCode
{
...
ExileServer_system_lootManager_dropItem = "\ExileLootDrop\ExileServer_system_lootManager_dropItem.sqf";
...
};

14
build.bat Normal file
View File

@ -0,0 +1,14 @@
set MSBUILD="C:\Program Files (x86)\MSBuild\14.0\Bin\MsBuild.exe"
set MAKEPBO="C:\Program Files (x86)\Mikero\DePboTools\bin\MakePbo.exe"
rd /s /q @ExileLootDrop
mkdir @ExileLootDrop\addons
%MSBUILD% src\ExileLootDrop.sln /property:Configuration=release /target:Rebuild /verbosity:normal /nologo
copy src\ExileLootDrop\bin\Release\ExileLootDrop.dll @ExileLootDrop
copy src\ExileLootDrop\bin\Release\ExileLootDrop.cfg @ExileLootDrop
copy LICENSE.txt @ExileLootDrop
copy README.txt @ExileLootDrop
%MAKEPBO% -U -W -P -@=ExileLootDrop sqf @ExileLootDrop\addons\ExileLootDrop.pbo

View File

@ -0,0 +1,39 @@
/**
* ExileServer_system_lootManager_dropItem
*
* maca134
* www.maca134.co.uk
* © 2016 maca134
*
* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.
* To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/4.0/.
*
* Examples:
* Get single item (returns string so is backwards compatible with Exile):
* _item = 'table' call ExileServer_system_lootManager_dropItem;
*
* Get multiple items (returns an array of items, this is good for mission stuff):
* _items = ['table', 10] call ExileServer_system_lootManager_dropItem;
*
*/
private _input = _this;
if (typeName _input == 'STRING') then {
_input = [_input, 1];
};
_input params [
['_table', '', ['']],
['_amount', 1, [0]]
];
private _packet = format['%1|%2', _table, _amount];
private _response = 'ExileLootDrop' callExtension _packet;
if (_response == 'ERROR') exitWith {
diag_log format['ExileLootDrop: Extension return error. Check logs! - %1', _packet];
""
};
private _return = if (_amount > 1) then {
_response splitString '|'
} else {
_response
};
_return

10
sqf/config.cpp Normal file
View File

@ -0,0 +1,10 @@
class CfgPatches {
class ExileLootDrop {
requiredVersion = 0.1;
requiredAddons[] = {"exile_server"};
units[] = {};
weapons[] = {};
magazines[] = {};
ammo[] = {};
};
};

28
src/ExileLootDrop.sln Normal file
View File

@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExileLootDrop", "ExileLootDrop\ExileLootDrop.csproj", "{F3DEC0AD-F490-4761-84A5-E51240FA3D91}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExileLootDropTester", "ExileLootDropTester\ExileLootDropTester.csproj", "{D4C0452C-055B-4773-920B-D9C7A70EB9D6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{F3DEC0AD-F490-4761-84A5-E51240FA3D91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F3DEC0AD-F490-4761-84A5-E51240FA3D91}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F3DEC0AD-F490-4761-84A5-E51240FA3D91}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F3DEC0AD-F490-4761-84A5-E51240FA3D91}.Release|Any CPU.Build.0 = Release|Any CPU
{D4C0452C-055B-4773-920B-D9C7A70EB9D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4C0452C-055B-4773-920B-D9C7A70EB9D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4C0452C-055B-4773-920B-D9C7A70EB9D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4C0452C-055B-4773-920B-D9C7A70EB9D6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,85 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
namespace ExileLootDrop
{
/// <summary>
/// Class to represent each Loot group in a config file
/// </summary>
public class CfgGroup
{
private const string BlockComments = @"/\*(.*?)\*/";
private const string LineComments = @"//[^\n]+";
/// <summary>
/// Load a loot config into a List of CfgGroups
/// </summary>
/// <param name="file">Path to loot config. If relative will start in the same directory as this extension</param>
/// <returns>CfgGroup List</returns>
public static List<CfgGroup> Load(string file)
{
if (!Path.IsPathRooted(file))
{
var basepath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
if (basepath != null)
file = Path.Combine(basepath, file);
}
if (!File.Exists(file))
throw new CfgGroupException($"File not found: {file}");
var lootcfg = Regex.Replace(File.ReadAllText(file),
BlockComments + "|" + LineComments,
"\n",
RegexOptions.Singleline);
var groups = new List<CfgGroup>();
CfgGroup group = null;
foreach (var line in lootcfg.Split('\r', '\n').Where(l => !string.IsNullOrWhiteSpace(l)))
{
if (line[0] == '>')
{
group = new CfgGroup(line.Substring(1).Trim());
groups.Add(group);
continue;
}
group?.Add(line);
}
return groups;
}
/// <summary>
/// Name of the group
/// </summary>
public string Name { get; }
/// <summary>
/// List of loot items
/// </summary>
public List<CfgGroupItem> Items { get; } = new List<CfgGroupItem>();
/// <summary>
/// CfgGroup constructor
/// </summary>
/// <param name="name">Group name</param>
public CfgGroup(string name)
{
Name = name;
}
/// <summary>
/// Add a loot item
/// </summary>
/// <param name="line">Line from the loot config</param>
internal void Add(string line)
{
try
{
Items.Add(new CfgGroupItem(line));
}
catch (CfgGroupItemException ex)
{
throw new CfgGroupException($"Failed to add GroupItem: {line}", ex);
}
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Runtime.Serialization;
namespace ExileLootDrop
{
[Serializable]
public class CfgGroupException : Exception
{
public CfgGroupException()
{
}
public CfgGroupException(string message) : base(message)
{
}
public CfgGroupException(string message, Exception innerException) : base(message, innerException)
{
}
protected CfgGroupException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

View File

@ -0,0 +1,31 @@
namespace ExileLootDrop
{
public class CfgGroupItem
{
/// <summary>
/// Item chance
/// </summary>
public int Chance { get; }
/// <summary>
/// Item/group name
/// </summary>
public string Item { get; }
/// <summary>
/// CfgGroupItem constructor
/// </summary>
/// <param name="line">Line from the loot config</param>
public CfgGroupItem(string line)
{
var parts = line.Split(',');
if (parts.Length != 2)
throw new CfgGroupItemException($"Item line is invalid: {line}");
int chance;
if (!int.TryParse(parts[0], out chance))
throw new CfgGroupItemException($"Could not parse chance: {line}");
Chance = chance;
Item = parts[1].Trim();
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Runtime.Serialization;
namespace ExileLootDrop
{
[Serializable]
public class CfgGroupItemException : CfgGroupException
{
public CfgGroupItemException()
{
}
public CfgGroupItemException(string message) : base(message)
{
}
public CfgGroupItemException(string message, Exception innerException) : base(message, innerException)
{
}
protected CfgGroupItemException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using RGiesecke.DllExport;
namespace ExileLootDrop
{
/// <summary>
/// Entry point class for ARMA
/// </summary>
public class DllEntry
{
/// <summary>
/// Entry point method for ARMA
/// </summary>
[DllExport("_RVExtension@12", CallingConvention = CallingConvention.Winapi)]
public static void RvExtension(StringBuilder output, int outputSize, [MarshalAs(UnmanagedType.LPStr)] string function)
{
try
{
output.Append(Loot.Invoke(function));
}
catch (Exception ex)
{
Logger.Log(Logger.Level.Error, "Uncaught Exception!");
Logger.Log(ex);
output.Append("ERROR");
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{F3DEC0AD-F490-4761-84A5-E51240FA3D91}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ExileLootDrop</RootNamespace>
<AssemblyName>ExileLootDrop</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Reference Include="RGiesecke.DllExport.Metadata, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8f52d83c1a22df51, processorArchitecture=MSIL">
<HintPath>..\packages\UnmanagedExports.1.2.7\lib\net\RGiesecke.DllExport.Metadata.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="DllEntry.cs" />
<Compile Include="Logger.cs" />
<Compile Include="Loot.cs" />
<Compile Include="CfgGroup.cs" />
<Compile Include="CfgGroupException.cs" />
<Compile Include="CfgGroupItemException.cs" />
<Compile Include="CfgGroupItem.cs" />
<Compile Include="LootTableNotFoundException.cs" />
<Compile Include="LootException.cs" />
<Compile Include="LootItem.cs" />
<Compile Include="LootTable.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="ExileLootDrop.cfg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="../packages/UnmanagedExports.1.2.7/tools/RGiesecke.DllExport.targets" Condition="Exists('../packages/UnmanagedExports.1.2.7/tools/RGiesecke.DllExport.targets')" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

441
src/ExileLootDrop/Logger.cs Normal file
View File

@ -0,0 +1,441 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace ExileLootDrop
{
internal static class Logger
{
private static readonly LogPublisher LogPublisher;
private static readonly object Sync = new object();
private static bool _isTurned = true;
private static bool _isTurnedDebug = true;
static Logger()
{
lock (Sync)
{
LogPublisher = new LogPublisher();
Modules = new ModuleManager();
Debug = new DebugLogger();
}
}
internal static Level DefaultLevel { get; set; } = Level.Info;
internal static ILoggerHandlerManager LoggerHandlerManager => LogPublisher;
internal static IEnumerable<LogMessage> Messages => LogPublisher.Messages;
internal static DebugLogger Debug { get; }
internal static ModuleManager Modules { get; }
internal static void Log()
{
Log("There is no message");
}
internal static void Log(string message)
{
Log(DefaultLevel, message);
}
internal static void Log(Level level, string message)
{
var stackFrame = FindStackFrame();
var methodBase = GetCallingMethodBase(stackFrame);
var callingMethod = methodBase.Name;
var callingClass = methodBase.ReflectedType?.Name;
var lineNumber = stackFrame.GetFileLineNumber();
Log(level, message, callingClass, callingMethod, lineNumber);
}
internal static void Log(Exception exception)
{
Log(Level.Error, exception.Message);
Modules.ExceptionLog(exception);
}
internal static void Log<TClass>(Exception exception) where TClass : class
{
var message = $"Log exception -> Message: {exception.Message}\nStackTrace: {exception.StackTrace}";
Log<TClass>(Level.Error, message);
}
internal static void Log<TClass>(string message) where TClass : class
{
Log<TClass>(DefaultLevel, message);
}
internal static void Log<TClass>(Level level, string message) where TClass : class
{
var stackFrame = FindStackFrame();
var methodBase = GetCallingMethodBase(stackFrame);
var callingMethod = methodBase.Name;
var callingClass = typeof (TClass).Name;
var lineNumber = stackFrame.GetFileLineNumber();
Log(level, message, callingClass, callingMethod, lineNumber);
}
private static void Log(Level level, string message, string callingClass, string callingMethod, int lineNumber)
{
if (!_isTurned || (!_isTurnedDebug && level == Level.Debug))
return;
var currentDateTime = DateTime.Now;
Modules.BeforeLog();
var logMessage = new LogMessage(level, message, currentDateTime, callingClass, callingMethod, lineNumber);
LogPublisher.Publish(logMessage);
Modules.AfterLog(logMessage);
}
private static MethodBase GetCallingMethodBase(StackFrame stackFrame)
{
return stackFrame == null
? MethodBase.GetCurrentMethod()
: stackFrame.GetMethod();
}
private static StackFrame FindStackFrame()
{
var stackTrace = new StackTrace();
var frames = stackTrace.GetFrames();
if (frames == null)
return null;
for (var i = 0; i < frames.Length; i++)
{
var methodBase = stackTrace.GetFrame(i).GetMethod();
var name = MethodBase.GetCurrentMethod().Name;
if (!methodBase.Name.Equals("Log") && !methodBase.Name.Equals(name))
return new StackFrame(i, true);
}
return null;
}
internal static void On()
{
_isTurned = true;
}
internal static void Off()
{
_isTurned = false;
}
internal static void DebugOn()
{
_isTurnedDebug = true;
}
internal static void DebugOff()
{
_isTurnedDebug = false;
}
internal enum Level
{
None,
Info,
Warning,
Error,
Severe,
Fine,
Debug
}
}
internal interface ILoggerFormatter
{
string ApplyFormat(LogMessage logMessage);
}
internal interface ILoggerHandlerManager
{
ILoggerHandlerManager AddHandler(ILoggerHandler loggerHandler);
bool RemoveHandler(ILoggerHandler loggerHandler);
}
internal interface ILoggerHandler
{
void Publish(LogMessage logMessage);
}
internal class LogMessage
{
public LogMessage()
{
}
public LogMessage(Logger.Level level, string text, DateTime dateTime, string callingClass, string callingMethod,
int lineNumber)
{
Level = level;
Text = text;
DateTime = dateTime;
CallingClass = callingClass;
CallingMethod = callingMethod;
LineNumber = lineNumber;
}
public DateTime DateTime { get; set; }
public Logger.Level Level { get; set; }
public string Text { get; set; }
public string CallingClass { get; set; }
public string CallingMethod { get; set; }
public int LineNumber { get; set; }
public override string ToString()
{
return new DefaultLoggerFormatter().ApplyFormat(this);
}
}
internal class DefaultLoggerFormatter : ILoggerFormatter
{
public string ApplyFormat(LogMessage logMessage)
{
return
$"{logMessage.DateTime:dd.MM.yyyy HH:mm}: {logMessage.Level} [line: {logMessage.LineNumber} {logMessage.CallingClass} -> {logMessage.CallingMethod}()]: {logMessage.Text}";
}
}
internal class DebugLogger
{
private const Logger.Level DebugLevel = Logger.Level.Debug;
public void Log()
{
Log("There is no message");
}
public void Log(string message)
{
Logger.Log(DebugLevel, message);
}
public void Log(Exception exception)
{
Logger.Log(DebugLevel, exception.Message);
}
public void Log<TClass>(Exception exception) where TClass : class
{
var message = $"Log exception -> Message: {exception.Message}\nStackTrace: {exception.StackTrace}";
Logger.Log<TClass>(DebugLevel, message);
}
public void Log<TClass>(string message) where TClass : class
{
Logger.Log<TClass>(DebugLevel, message);
}
}
internal class ModuleManager
{
private readonly IDictionary<string, LoggerModule> _modules;
public ModuleManager()
{
_modules = new Dictionary<string, LoggerModule>();
}
public void BeforeLog()
{
foreach (var loggerModule in _modules.Values)
loggerModule.BeforeLog();
}
public void AfterLog(LogMessage logMessage)
{
foreach (var loggerModule in _modules.Values)
loggerModule.AfterLog(logMessage);
}
public void ExceptionLog(Exception exception)
{
foreach (var loggerModule in _modules.Values)
loggerModule.ExceptionLog(exception);
}
public void Install(LoggerModule module)
{
while (true)
{
if (!_modules.ContainsKey(module.Name))
{
module.Initialize();
_modules.Add(module.Name, module);
}
else
{
// reinstall module
Uninstall(module.Name);
continue;
}
break;
}
}
public void Uninstall(LoggerModule module)
{
if (_modules.ContainsKey(module.Name))
_modules.Remove(module.Name);
}
public void Uninstall(string moduleName)
{
if (_modules.ContainsKey(moduleName))
_modules.Remove(moduleName);
}
}
internal abstract class LoggerModule
{
public abstract string Name { get; }
public virtual void BeforeLog()
{
}
public virtual void AfterLog(LogMessage logMessage)
{
}
public virtual void ExceptionLog(Exception exception)
{
}
public virtual void Initialize()
{
}
}
internal class LogPublisher : ILoggerHandlerManager
{
private readonly IList<ILoggerHandler> _loggerHandlers;
private readonly IList<LogMessage> _messages;
public LogPublisher()
{
_loggerHandlers = new List<ILoggerHandler>();
_messages = new List<LogMessage>();
}
public IEnumerable<LogMessage> Messages => _messages;
public ILoggerHandlerManager AddHandler(ILoggerHandler loggerHandler)
{
if (loggerHandler != null)
_loggerHandlers.Add(loggerHandler);
return this;
}
public bool RemoveHandler(ILoggerHandler loggerHandler)
{
return _loggerHandlers.Remove(loggerHandler);
}
public void Publish(LogMessage logMessage)
{
_messages.Add(logMessage);
foreach (var loggerHandler in _loggerHandlers)
loggerHandler.Publish(logMessage);
}
}
internal class ConsoleLoggerHandler : ILoggerHandler
{
private readonly ILoggerFormatter _loggerFormatter;
internal ConsoleLoggerHandler() : this(new DefaultLoggerFormatter())
{
}
internal ConsoleLoggerHandler(ILoggerFormatter loggerFormatter)
{
_loggerFormatter = loggerFormatter;
}
public void Publish(LogMessage logMessage)
{
Console.WriteLine(_loggerFormatter.ApplyFormat(logMessage));
}
}
internal class FileLoggerHandler : ILoggerHandler
{
private static readonly object Sync = new object();
private readonly string _directory;
private readonly string _fileName;
private readonly ILoggerFormatter _loggerFormatter;
internal FileLoggerHandler() : this(CreateFileName())
{
}
internal FileLoggerHandler(string fileName) : this(fileName, string.Empty)
{
}
internal FileLoggerHandler(string fileName, string directory)
: this(new DefaultLoggerFormatter(), fileName, directory)
{
}
internal FileLoggerHandler(ILoggerFormatter loggerFormatter) : this(loggerFormatter, CreateFileName())
{
}
internal FileLoggerHandler(ILoggerFormatter loggerFormatter, string fileName)
: this(loggerFormatter, fileName, string.Empty)
{
}
internal FileLoggerHandler(ILoggerFormatter loggerFormatter, string fileName, string directory)
{
_loggerFormatter = loggerFormatter;
_fileName = fileName;
_directory = directory;
}
public void Publish(LogMessage logMessage)
{
if (!string.IsNullOrEmpty(_directory))
{
var directoryInfo = new DirectoryInfo(Path.Combine(_directory));
if (!directoryInfo.Exists)
directoryInfo.Create();
}
try
{
lock (Sync)
{
using (
var writer = new StreamWriter(File.Open(Path.Combine(_directory, _fileName), FileMode.Append)))
writer.WriteLine(_loggerFormatter.ApplyFormat(logMessage));
}
}
catch
{
// ignore
}
}
private static string CreateFileName()
{
var currentDate = DateTime.Now;
var guid = Guid.NewGuid();
return
$"Log_{currentDate.Year:0000}{currentDate.Month:00}{currentDate.Day:00}-{currentDate.Hour:00}{currentDate.Minute:00}_{guid}.log";
}
}
}

174
src/ExileLootDrop/Loot.cs Normal file
View File

@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace ExileLootDrop
{
public class Loot
{
private static bool _hasErrored;
/// <summary>
/// Extension path
/// </summary>
public static string BasePath => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? "";
/// <summary>
/// Max depth for table groups
/// </summary>
public static int MaxDepth { get; set; } = 50;
/// <summary>
/// Loot tables
/// </summary>
public static Loot LootTable { get; } = new Loot();
/// <summary>
/// Static method for ARMA's callExtension
/// </summary>
/// <param name="input">Input from ARMA</param>
/// <returns>Results to send back to ARMA</returns>
public static string Invoke(string input)
{
if (_hasErrored)
{
Logger.Log<Loot>(Logger.Level.Error, "Trying to run ext after an error has occured!");
return "ERROR";
}
var parts = input.Split('|');
var table = parts[0];
var count = (parts.Length == 2) ? int.Parse(parts[1]) : 1;
try
{
return string.Join("|", LootTable.GetItems(table, count));
}
catch (LootTableNotFoundException ex)
{
Logger.Log<Loot>(Logger.Level.Error, $"Looks like you tried to get loot from a table that didnt exist: {input}");
Logger.Log<Loot>(ex);
return "ERROR";
}
}
private readonly List<CfgGroup> _cfgGroups;
private Dictionary<string, LootTable> Table { get; } = new Dictionary<string, LootTable>();
/// <summary>
/// Loot constructor
/// </summary>
private Loot()
{
const string cfgpath = "ExileLootDrop.cfg";
Logger.LoggerHandlerManager.AddHandler(new ConsoleLoggerHandler());
var logfile = Path.Combine(BasePath, "output.log");
try
{
if (File.Exists(logfile))
{
File.Delete(logfile);
}
}
catch
{
// ignore
}
Logger.LoggerHandlerManager.AddHandler(new FileLoggerHandler(logfile));
Logger.Log<Loot>(Logger.Level.Info, "=====================================================================");
Logger.Log<Loot>(Logger.Level.Info, "Exile Loot Drop Extension");
Logger.Log<Loot>(Logger.Level.Info, "By maca134");
Logger.Log<Loot>(Logger.Level.Info, "Please remember to donate for more cool shit!");
Logger.Log<Loot>(Logger.Level.Info, "http://maca134.co.uk");
Logger.Log<Loot>(Logger.Level.Info, "=====================================================================");
var start = DateTime.Now;
try
{
_cfgGroups = CfgGroup.Load(cfgpath);
}
catch (CfgGroupException ex)
{
_hasErrored = true;
Logger.Log<Loot>(Logger.Level.Error, $"There was an error loading the loot cfg {cfgpath}: {ex.Message}");
throw new LootException($"There was an error loading the loot cfg {cfgpath}", ex);
}
foreach (var group in _cfgGroups)
{
var list = FlattenGroups(group);
var table = new LootTable(group.Name, list);
Table.Add(group.Name, table);
}
var span = DateTime.Now - start;
Logger.Log<Loot>($"Took {span.Milliseconds}ms to load and parse loot cfg");
}
/// <summary>
/// Flattens cfg groups (recursive)
/// </summary>
/// <param name="group">Base group</param>
/// <param name="depth">Current depth</param>
/// <returns></returns>
private List<LootItem> FlattenGroups(CfgGroup group, int depth = 0)
{
var list = new List<LootItem>();
if (depth > MaxDepth)
{
Logger.Log<Loot>(Logger.Level.Warning, $"The loot table is too deep: {depth} > {MaxDepth}");
return list;
}
var total = group.Items.Select(i => i.Chance).Sum();
foreach (var item in group.Items.OrderByDescending(a => a.Chance))
{
var chance = (decimal)item.Chance / total;
var child = _cfgGroups.Find(g => g.Name == item.Item);
if (child != null)
{
var childlist = FlattenGroups(child, depth++);
childlist.ForEach(i =>
{
list.Add(new LootItem
{
Item = i.Item,
Chance = i.Chance * chance
});
});
continue;
}
list.Add(new LootItem
{
Item = item.Item,
Chance = chance
});
}
return list;
}
/// <summary>
/// Drops X amount of loot items from table
/// </summary>
/// <param name="table">Loot table name</param>
/// <param name="count">Amount of items to return</param>
/// <returns></returns>
public string[] GetItems(string table, int count = 1)
{
if (!Table.ContainsKey(table))
throw new LootTableNotFoundException($"No loot table called {table}");
var items = new List<string>();
for (var i = 0; i < count; i++)
items.Add(Table[table].Drop());
return items.ToArray();
}
/// <summary>
/// Get tables
/// </summary>
/// <returns>List of table names</returns>
public string[] GetTables()
{
return LootTable.Table.Keys.ToArray();
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Runtime.Serialization;
namespace ExileLootDrop
{
[Serializable]
internal class LootException : Exception
{
public LootException()
{
}
public LootException(string message) : base(message)
{
}
public LootException(string message, Exception innerException) : base(message, innerException)
{
}
protected LootException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

View File

@ -0,0 +1,20 @@
namespace ExileLootDrop
{
public class LootItem
{
/// <summary>
/// Item classname
/// </summary>
public string Item { get; set; }
/// <summary>
/// Chance for item
/// </summary>
public decimal Chance { get; set; }
/// <summary>
/// Cumulative
/// </summary>
public decimal Sum { get; set; }
}
}

View File

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
namespace ExileLootDrop
{
public class LootTable
{
/// <summary>
/// Loot table name
/// </summary>
public string Name { get; }
/// <summary>
/// List of loot items
/// </summary>
public LootItem[] LootItems { get; }
private readonly Random _rnd = new Random();
/// <summary>
/// LootTable constuctor
/// </summary>
/// <param name="name">Table name</param>
/// <param name="lootList">Item list</param>
public LootTable(string name, List<LootItem> lootList)
{
Name = name;
var sum = 0m;
lootList.ForEach(i =>
{
sum += i.Chance;
i.Sum = sum;
});
LootItems = lootList.ToArray();
}
/// <summary>
/// Drop a loot item
/// </summary>
/// <returns>Item classname</returns>
public string Drop()
{
var rnd = (decimal)_rnd.NextDouble();
foreach (var item in LootItems)
{
if (item.Sum >= rnd)
return item.Item;
}
throw new LootException("Erm shouldnt be here... rnd more than 1? C# is broken");
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Runtime.Serialization;
namespace ExileLootDrop
{
[Serializable]
internal class LootTableNotFoundException : LootException
{
public LootTableNotFoundException()
{
}
public LootTableNotFoundException(string message) : base(message)
{
}
public LootTableNotFoundException(string message, Exception innerException) : base(message, innerException)
{
}
protected LootTableNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

View File

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("DropItem")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("DropItem")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f3dec0ad-f490-4761-84a5-e51240fa3d91")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="UnmanagedExports" version="1.2.7" targetFramework="net452" />
</packages>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
</startup>
</configuration>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{D4C0452C-055B-4773-920B-D9C7A70EB9D6}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ExileLootDropTester</RootNamespace>
<AssemblyName>ExileLootDropTester</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>x86</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="ExileLootDrop.cfg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ExileLootDrop\ExileLootDrop.csproj">
<Project>{f3dec0ad-f490-4761-84a5-e51240fa3d91}</Project>
<Name>ExileLootDrop</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ExileLootDrop;
namespace ExileLootDropTester
{
class Program
{
const int Loops = 1000000;
static void Main(string[] args)
{
Console.WriteLine("Loading Loot");
//Loot.LootTable
Console.WriteLine();
var tables = Loot.LootTable.GetTables();
for (var i = 0; i < tables.Length; i++)
{
var table = tables[i];
Console.WriteLine($"{i}: {table}");
}
Console.WriteLine();
Console.WriteLine("Select a table: ");
var input = Console.ReadLine();
Console.WriteLine();
int index;
if (!int.TryParse(input, out index))
{
Console.WriteLine("input error");
return;
}
if (index >= tables.Length)
{
Console.WriteLine("input out of range");
return;
}
var selectedTable = tables[index];
Console.WriteLine($"\"{selectedTable}\" selected");
Console.WriteLine($"Calculating Loot - Running {Loops} iterations...");
Console.WriteLine();
var lootdrops = new Dictionary<string, int>();
var start = DateTime.Now;
for (var i = 0; i < Loops - 1; i++)
{
var item = Loot.Invoke(selectedTable);
if (!lootdrops.ContainsKey(item))
lootdrops[item] = 0;
lootdrops[item]++;
}
var timetaken = DateTime.Now - start;
var items = from pair in lootdrops
orderby pair.Value descending
select pair;
foreach (var pair in items)
{
Console.WriteLine("{0}: {1}", pair.Key, pair.Value);
}
Console.WriteLine();
Console.WriteLine($"Took {timetaken.TotalMilliseconds}ms to do {Loops} items - {timetaken.TotalMilliseconds / Loops}ms per item");
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
}
}

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("DropItemTester")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("DropItemTester")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("d4c0452c-055b-4773-920b-d9c7a70eb9d6")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]