Merge pull request #1256 from acemod/medicalExtension

Medical Extension for handleDamage wounds
This commit is contained in:
Glowbal 2015-05-29 23:16:00 +02:00
commit 552b2f390f
15 changed files with 620 additions and 100 deletions

BIN
ace_medical.dll Normal file

Binary file not shown.

View File

@ -21,3 +21,7 @@ class CfgPatches {
#include "ACE_Settings.hpp"
#include "UI\RscTitles.hpp"
#include "UI\triagecard.hpp"
class ACE_Extensions {
extensions[] += {"ace_medical"};
};

View File

@ -24,120 +24,43 @@ _damage = _this select 2;
_typeOfProjectile = _this select 3;
_typeOfDamage = _this select 4;
// Convert the selectionName to a number and ensure it is a valid selection.
_bodyPartn = [_selectionName] call FUNC(selectionNameToNumber);
if (_bodyPartn < 0) exitwith {};
// Get the injury type information. Format: [typeDamage thresholds, selectionSpecific, woundTypes]
_injuryTypeInfo = missionNamespace getvariable [format[QGVAR(woundInjuryType_%1), _typeOfDamage],[[], false, []]];
// This are the available injuries for this damage type. Format [[classtype, selections, bloodloss, minimalDamage, pain], ..]
_allInjuriesForDamageType = _injuryTypeInfo select 2;
// It appears we are dealing with an unknown type of damage.
if (count _allInjuriesForDamageType == 0) then {
// grabbing the configuration for unknown damage type
_injuryTypeInfo = missionNamespace getvariable [QGVAR(woundInjuryType_unknown),[[], false, []]];
_allInjuriesForDamageType = _injuryTypeInfo select 2;
};
// find the available injuries for this damage type and damage amount
_highestPossibleSpot = -1;
_highestPossibleDamage = -1;
_allPossibleInjuries = [];
{
_damageLevels = _x select 4;
_minDamage = _damageLevels select 0;
_maxDamage = _damageLevels select 1;
// Check if the damage is higher as the min damage for the specific injury
if (_damage >= _minDamage && {_damage <= _maxDamage || _maxDamage < 0}) then {
//_classType = _x select 0;
_selections = _x select 1;
//_bloodLoss = _x select 2;
//_pain = _x select 3;
// Check if the injury can be applied to the given selection name
if ("All" in _selections || _selectionName in _selections) then {
// Find the wound which has the highest minimal damage, so we can use this later on for adding the correct injuries
if (_minDamage > _highestPossibleDamage) then {
_highestPossibleSpot = _foreachIndex;
_highestPossibleDamage = _minDamage;
};
// Store the valid possible injury for the damage type, damage amount and selection
_allPossibleInjuries pushback _x;
};
};
}foreach _allInjuriesForDamageType;
// No possible wounds available for this damage type or damage amount.
if (_highestPossibleSpot < 0) exitwith {};
// Administration for open wounds and ids
_openWounds = _unit getvariable[QGVAR(openWounds), []];
_woundID = _unit getvariable[QGVAR(lastUniqueWoundID), 1];
_extensionOutput = "ace_medical" callExtension format ["HandleDamageWounds,%1,%2,%3,%4", _selectionName, _damage, _typeOfDamage, _woundID];
_painToAdd = 0;
_woundsCreated = [];
call compile _extensionOutput;
_foundIndex = -1;
{
if (_x select 0 <= _damage) exitwith {
for "_i" from 0 to (1+ floor(random(_x select 1)-1)) /* step +1 */ do {
// Find the injury we are going to add. Format [ classID, allowdSelections, bloodloss, painOfInjury, minimalDamage]
_toAddInjury = if (random(1) >= 0.85) then {_allInjuriesForDamageType select _highestPossibleSpot} else {_allPossibleInjuries select (floor(random (count _allPossibleInjuries)));};
_toAddClassID = _toAddInjury select 0;
_foundIndex = -1;
_bodyPartNToAdd = if (_injuryTypeInfo select 1) then {_bodyPartn} else {floor(random(6))};
// If the injury type is selection part specific, we will check if one of those injury types already exists and find the spot for it..
if ((_injuryTypeInfo select 1)) then {
{
// Check if we have an id of the given class on the given bodypart already
if (_x select 1 == _toAddClassID && {_x select 2 == _bodyPartNToAdd}) exitwith {
_foundIndex = _foreachIndex;
};
}foreach _openWounds;
};
_injury = [];
if (_foundIndex < 0) then {
// Create a new injury. Format [ID, classID, bodypart, percentage treated, bloodloss rate]
_injury = [_woundID, _toAddInjury select 0, _bodyPartNToAdd, 1, _toAddInjury select 2];
// Since it is a new injury, we will have to add it to the open wounds array to store it
_openWounds pushback _injury;
// New injuries will also increase the wound ID
_woundID = _woundID + 1;
} else {
// We already have one of these, so we are just going to increase the number that we have of it with a new one.
_injury = _openWounds select _foundIndex;
_injury set [3, (_injury select 3) + 1];
};
// Store the injury so we can process it later correctly.
_woundsCreated pushback _injury;
// Collect the pain that is caused by this injury
_painToAdd = _painToAdd + (_toAddInjury select 3);
_toAddClassID = _x select 1;
_bodyPartNToAdd = _x select 2;
{
// Check if we have an id of the given class on the given bodypart already
if (_x select 1 == _toAddClassID && {_x select 2 == _bodyPartNToAdd}) exitwith {
_foundIndex = _foreachIndex;
};
};
}foreach (_injuryTypeInfo select 0); // foreach damage thresholds
}foreach _openWounds;
_unit setvariable [QGVAR(openWounds), _openWounds, !USE_WOUND_EVENT_SYNC];
if (_foundIndex < 0) then {
// Since it is a new injury, we will have to add it to the open wounds array to store it
_openWounds pushback _x;
} else {
// We already have one of these, so we are just going to increase the number that we have of it with a new one.
_injury = _openWounds select _foundIndex;
_injury set [3, (_injury select 3) + 1];
};
}foreach _woundsCreated;
_unit setvariable [QGVAR(openWounds), _openWounds, true];
// Only update if new wounds have been created
if (count _woundsCreated > 0) then {
_unit setvariable [QGVAR(lastUniqueWoundID), _woundID, true];
};
if (USE_WOUND_EVENT_SYNC) then {
// Broadcast the new injuries across the net in parts. One broadcast per injury. Prevents having to broadcast one massive array of injuries.
{
["medical_propagateWound", [_unit, _x]] call EFUNC(common,globalEvent);
}foreach _woundsCreated;
};
_painLevel = _unit getvariable [QGVAR(pain), 0];
_unit setvariable [QGVAR(pain), _painLevel + _painToAdd];

View File

@ -118,4 +118,58 @@ _selectionSpecific = getNumber(_damageTypesConfig >> "selectionSpecific");
if (isNumber(_damageTypesConfig >> _x >> "selectionSpecific")) then { _selectionSpecificType = getNumber(_damageTypesConfig >> _x >> "selectionSpecific");};
};
missionNamespace setvariable [_varName, [_typeThresholds, _selectionSpecificType > 0, _woundTypes]];
private ["_minDamageThresholds", "_amountThresholds"];
// extension loading
_minDamageThresholds = "";
_amountThresholds = "";
{
_minDamageThresholds = _minDamageThresholds + str(_x select 0);
_amountThresholds = _amountThresholds + str(_x select 1);
if (_forEachIndex < (count _typeThresholds) - 1) then {
_minDamageThresholds = _minDamageThresholds + ":";
_amountThresholds = _amountThresholds + ":";
};
}foreach _typeThresholds;
"ace_medical" callExtension format ["addDamageType,%1,%2,%3,%4,%5", _type, GVAR(minLethalDamages) select _foreachIndex, _minDamageThresholds, _amountThresholds, _selectionSpecificType];
}foreach _allFoundDamageTypes;
// Extension loading
{
private ["_classID", "_className", "_allowedSelections", "_bloodLoss", "_pain", "_minDamage", "_maxDamage", "_causes", "_classDisplayName", "_extensionInput", "_selections", "_causesArray"];
// add shit to addInjuryType
_classID = _x select 0;
_className = GVAR(woundClassNames) select _forEachIndex;
_allowedSelections = "";
_selections = _x select 1;
{
_allowedSelections = _allowedSelections + _x;
if (_forEachIndex < (count _selections) - 1) then {
_allowedSelections = _allowedSelections + ":";
};
}foreach _selections;
_bloodLoss = _x select 2;
_pain = _x select 3;
_minDamage = (_x select 4) select 0;
_maxDamage = (_x select 4) select 1;
_causes = "";
_causesArray = (_x select 5);
{
_causes = _causes + _x;
if (_forEachIndex < (count _causesArray) - 1) then {
_causes = _causes + ":";
};
}foreach _causesArray;
_classDisplayName = _x select 6;
"ace_medical" callExtension format["addInjuryType,%1,%2,%3,%4,%5,%6,%7,%8,%9", _classID, _className, _allowedSelections, _bloodLoss, _pain, _minDamage, _maxDamage, _causes, _classDisplayName];
}foreach _allWoundClasses;
"ace_medical" callExtension "ConfigComplete";

View File

@ -125,6 +125,7 @@ add_subdirectory(fcs)
add_subdirectory(break_line)
add_subdirectory(clipboard)
add_subdirectory(advanced_ballistics)
add_subdirectory(medical)
# Test Extension for dynamically loading/unloading built extensions; does not build in release
if (DEVEL)

View File

@ -0,0 +1,13 @@
set(ACE_EXTENSION_NAME "ace_medical")
file(GLOB SOURCES *.h *.hpp *.c *.cpp)
add_library( ${ACE_EXTENSION_NAME} SHARED ${SOURCES} ${GLOBAL_SOURCES})
target_link_libraries(${ACE_EXTENSION_NAME} ace_common)
set_target_properties(${ACE_EXTENSION_NAME} PROPERTIES PREFIX "")
set_target_properties(${ACE_EXTENSION_NAME} PROPERTIES FOLDER Extensions)
if(CMAKE_COMPILER_IS_GNUCXX)
set_target_properties(${ACE_EXTENSION_NAME} PROPERTIES LINK_SEARCH_START_STATIC 1)
set_target_properties(${ACE_EXTENSION_NAME} PROPERTIES LINK_SEARCH_END_STATIC 1)
endif()

View File

@ -0,0 +1,11 @@
#include "DamageType.h"
ace::medical::injuries::DamageType::DamageType(std::string aTypeName, double minimalLethalDamage, std::vector<double> minDamage, std::vector<double> amountOfInjuresOnDamage, bool specificOnly)
: typeName(aTypeName), minLethalDamage(minimalLethalDamage), minDamageThreshold(minDamage), amountOfInjuresOnDamage(amountOfInjuresOnDamage), selectionSpecific(specificOnly)
{
}
ace::medical::injuries::DamageType::~DamageType()
{
}

View File

@ -0,0 +1,28 @@
#include <string>
#include <vector>
#include <memory>
namespace ace {
namespace medical {
namespace injuries {
class InjuryType;
class DamageType
{
public:
DamageType(std::string aTypeName, double minimalLethalDamage, std::vector<double> minDamageThreshold, std::vector<double> amountOfInjuresOnDamage, bool specificOnly);
~DamageType();
std::string typeName;
double minLethalDamage;
std::vector<double> minDamageThreshold;
std::vector<double> amountOfInjuresOnDamage;
bool selectionSpecific;
std::vector<std::shared_ptr<InjuryType>> possibleInjuries;
};
}
}
}

View File

@ -0,0 +1,13 @@
#include "InjuryType.h"
#include "DamageType.h"
ace::medical::injuries::InjuryType::InjuryType(signed int anId, const std::string& aClassname, std::vector<std::string>& allowedSelections, double theBloodLoss, double thePain, double minimumDamage, double maximumDamage, std::vector<std::string>& possibleCauses, std::string& aDisplayname)
: ID(anId), className(aClassname), selections(allowedSelections), bloodLoss(theBloodLoss), pain(thePain), minDamage(minimumDamage), maxDamage(maximumDamage), causes(possibleCauses), displayName(aDisplayname)
{
}
ace::medical::injuries::InjuryType::~InjuryType()
{
}

View File

@ -0,0 +1,27 @@
#include <string>
#include <vector>
namespace ace {
namespace medical {
namespace injuries {
class DamageType;
class InjuryType
{
public:
InjuryType(signed int anId, const std::string& aClassname, std::vector<std::string>& allowedSelections, double theBloodLoss, double thePain, double minimumDamage, double maximumDamage, std::vector<std::string>& possibleCauses, std::string& aDisplayname);
~InjuryType();
signed int ID;
std::string className;
std::vector<std::string> selections;
double bloodLoss;
double pain;
double minDamage;
double maxDamage;
std::vector<std::string> causes;
std::string displayName;
};
}
}
}

View File

@ -0,0 +1,18 @@
#include "OpenWound.h"
#include <sstream>
ace::medical::injuries::OpenWound::OpenWound(int anID, int aClassID, int aBodyPartId, double aPercentage, double aBl, double painAmount) :
woundID(anID), classID(aClassID), bodyPart(aBodyPartId), percentage(aPercentage), bloodlossRate(aBl), pain(painAmount)
{
}
ace::medical::injuries::OpenWound::~OpenWound()
{
}
std::string ace::medical::injuries::OpenWound::AsString()
{
std::stringstream stream;
stream << "[" << woundID << "," << classID << "," << bodyPart << "," << 1 << "," << bloodlossRate << "]";
return stream.str();
}

View File

@ -0,0 +1,26 @@
#pragma once
#include <string>
namespace ace {
namespace medical {
namespace injuries {
class OpenWound
{
public:
OpenWound(int anID, int aClassID, int aBodyPartId, double aPercentage, double aBl, double painAmount);
~OpenWound();
std::string AsString();
int woundID;
int classID;
int percentage;
double bodyPart;
double bloodlossRate;
double pain;
};
}
}
}

View File

@ -0,0 +1,242 @@
#include "handleDamage.h"
#include "OpenWound.h"
#include "DamageType.h"
#include "InjuryType.h"
#include <sstream>
#include <algorithm>
namespace ace {
namespace medical {
handleDamage::handleDamage()
{
}
handleDamage& handleDamage::GetInstance()
{
static handleDamage instance;
return instance;
}
handleDamage::~handleDamage()
{
}
std::string handleDamage::HandleDamageWounds(const std::string& selectionName, double amountOfDamage, const std::string& typeOfDamage, int woundID)
{
std::vector<ace::medical::injuries::OpenWound> wounds;
int selectionN = SelectionToNumber(selectionName);
std::stringstream stream;
if (selectionN >= 0)
{
double painToAdd = 0;
wounds = GetInjuryInfoFor(typeOfDamage, amountOfDamage, selectionN, woundID);
stream << "_woundsCreated = [";
for (int i = 0; i < wounds.size(); ++i)
{
stream << wounds.at(i).AsString();
if (i != wounds.size() - 1)
{
stream << ",";
}
painToAdd += wounds.at(i).pain;
}
stream << "];";
stream << "_painToAdd = " << painToAdd << ";";
return stream.str();
}
return stream.str();
}
std::vector<ace::medical::injuries::OpenWound> handleDamage::GetInjuryInfoFor(const std::string& typeOfDamage, double amountOfDamage, int selection, int woundID)
{
std::vector<ace::medical::injuries::OpenWound> injuriesToAdd;
std::vector<std::shared_ptr<ace::medical::injuries::InjuryType>> information;
std::shared_ptr<ace::medical::injuries::InjuryType> highestSpot = nullptr;
for (auto & damageType : damageTypes)
{
if (damageType->typeName == typeOfDamage)
{
for (auto & possibleInjury : damageType->possibleInjuries)
{
if (amountOfDamage >= possibleInjury->minDamage && (amountOfDamage <= possibleInjury->maxDamage || possibleInjury->maxDamage <= 0))
{
if (highestSpot == NULL)
highestSpot = possibleInjury;
if (possibleInjury->minDamage > highestSpot->minDamage)
highestSpot = possibleInjury;
information.push_back(possibleInjury);
}
}
if (highestSpot == NULL) {
break;
}
int c = 0;
for (double & threshold : damageType->minDamageThreshold)
{
if (amountOfDamage >= threshold)
{
double amountOfInjuriesOnDamage = damageType->amountOfInjuresOnDamage.at(c);
for (double injuryAmount = 0; injuryAmount < amountOfInjuriesOnDamage; ++injuryAmount)
{
std::shared_ptr<ace::medical::injuries::InjuryType> injuryToAdd;
if (rand() % 100 >= 85)
{
injuryToAdd = highestSpot;
}
else
{
injuryToAdd = information.at(0);
}
int bodyPartID = selection;
if (!damageType->selectionSpecific)
{
bodyPartID = rand() % 6;
}
injuries::OpenWound newWound(woundID++, injuryToAdd->ID, bodyPartID, 1, injuryToAdd->bloodLoss, injuryToAdd->pain);
injuriesToAdd.push_back(newWound);
}
}
++c;
}
return injuriesToAdd;
}
}
return injuriesToAdd;
}
std::string handleDamage::AddDamageType(const std::vector<std::string>& input)
{
if (input.size() == 5)
{
std::string typeName = input[0];
double minimalLethalDamage = std::stod(input[1]);
std::vector<double> minDamageThreshold = inputToVectorDouble(input[2]);
std::vector<double> amountOfInjuresOnDamage = inputToVectorDouble(input[3]);
bool selectionSpecific = std::stod(input[4]) > 0;
std::shared_ptr<ace::medical::injuries::DamageType> type(new ace::medical::injuries::DamageType(typeName, minimalLethalDamage, minDamageThreshold, amountOfInjuresOnDamage, selectionSpecific));
damageTypes.push_back(type);
std::stringstream stream;
stream << "ADDED: " << typeName << " - " << minimalLethalDamage << " - [";
for (double & sel : minDamageThreshold)
{
stream << sel << " -";
}
stream << "] - [";
for (double & sel : amountOfInjuresOnDamage)
{
stream << sel << " -";
}
stream << "] - " << selectionSpecific;
return stream.str();
}
return "failed";
}
std::string handleDamage::AddInjuryType(const std::vector<std::string>& input)
{
if (input.size() == 9)
{
int ID = std::stod(input[0]);
std::string className = input[1];
std::vector<std::string> allowedSelections = inputToVector(input[2]);
double bloodLoss = std::stod(input[3]);
double pain = std::stod(input[4]);
double minDamage = std::stod(input[5]);
double maxDamage = std::stod(input[6]);
std::vector<std::string> possibleCauses = inputToVector(input[7]);
std::string displayName = input[8];
std::shared_ptr<ace::medical::injuries::InjuryType> type(new ace::medical::injuries::InjuryType(ID, className, allowedSelections, bloodLoss, pain, minDamage, maxDamage, possibleCauses, displayName));
injuryTypes.push_back(type);
std::stringstream stream;
stream << "ADDED: " << ID << " - " << className << " - [";
for (std::string & sel : allowedSelections)
{
stream << sel << " -";
}
stream << "] - ";
stream << bloodLoss << " - " << pain << " - " << minDamage << " - " << maxDamage;
for (std::string & sel : possibleCauses)
{
stream << sel << " -";
}
stream << displayName;
return stream.str();
}
return "failed";
}
void handleDamage::FinalizeDefinitions()
{
// We are finding all possible injuries for a specific damage type here, so we don't have to figure that out at a later stage.
for (auto & damageType : damageTypes)
{
for (auto & injuryType : injuryTypes)
{
std::vector<std::string>::iterator it = std::find(injuryType->causes.begin(), injuryType->causes.end(), damageType->typeName);
// outputstream << " Evaluating causes: " << (it != injuryType->causes.end()) << " ";
if (it != injuryType->causes.end())
{
damageType->possibleInjuries.push_back(injuryType);
}
}
}
}
int handleDamage::SelectionToNumber(const std::string& selectionName)
{
// TODO use dynamic selections instead
std::vector<std::string> selections = { "head", "body", "hand_l", "hand_r", "leg_l", "leg_r" };
std::vector<std::string>::iterator it = find(selections.begin(), selections.end(), selectionName);
if (it != selections.end())
{
return it - selections.begin();
}
else
{
return -1; // TODO throw exception
}
}
std::vector<std::string> handleDamage::inputToVector(const std::string& input)
{
std::istringstream ss(input);
std::string token;
std::vector<std::string> output;
while (std::getline(ss, token, ':')) {
output.push_back(token);
}
return output;
}
std::vector<double> handleDamage::inputToVectorDouble(const std::string& input)
{
std::istringstream ss(input);
std::string token;
std::vector<double> output;
while (std::getline(ss, token, ':')) {
output.push_back(std::stod(token));
}
return output;
}
}
}

View File

@ -0,0 +1,92 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
namespace ace {
namespace medical {
namespace injuries {
class DamageType;
class InjuryType;
class OpenWound;
}
class handleDamage
{
public:
/**
*
*/
static handleDamage& GetInstance();
~handleDamage();
/**
* Find new open wounds from advanced medical based upon the received damage.
*
* @param selectionName A string representation of the bodypart (for example: leg_r)
* @param amountOfDamage The damage received
* @param typeOfDamage The type of the damage received
* @param woundID latest wound ID
* @return SQF string containing _woundsCreated and _painAdded.
*/
std::string HandleDamageWounds(const std::string& selectionName, double amountOfDamage, const std::string& typeOfDamage, int woundID);
/**
* Find new open wounds from advanced medical based upon the received damage.
*
* @param typeOfDamage The type of the damage received
* @param amountOfDamage The damage received
* @param selection A number representation of the bodypart. Number from 0 to 6.
* @param woundID latest wound ID
* @return New open wounds.
*/
std::vector<ace::medical::injuries::OpenWound> GetInjuryInfoFor(const std::string& typeOfDamage, double amountOfDamage, int selection, int woundID);
/**
* Add a new damage type from extension input parameters
*
* @param sqfDamageTypeDefinition
* @return String with result of addition: ADDED [parameters] or FAILED
*/
std::string AddDamageType(const std::vector<std::string>& sqfDamageTypeDefinition);
/**
* Add a new injury type from extension input parameters
*
* @param sqfDamageTypeDefinition
* @return String with result of addition: ADDED [parameters] or FAILED
*/
std::string AddInjuryType(const std::vector<std::string>& sqfInjuryDefinition);
/**
* Convert a selectionName to a number
*
* @param selectionName
* @return number of selection
*/
int SelectionToNumber(const std::string& selectionName);
/**
* Lets the system know that all data has been added
*/
void FinalizeDefinitions();
private:
handleDamage();
handleDamage(handleDamage const&) = delete;
void operator=(handleDamage const&) = delete;
std::vector<std::string> inputToVector(const std::string& input);
std::vector<double> inputToVectorDouble(const std::string& input);
std::vector<std::shared_ptr<ace::medical::injuries::DamageType>> damageTypes;
std::vector<std::shared_ptr<ace::medical::injuries::InjuryType>> injuryTypes;
std::vector<std::string> selections;
std::vector<std::string> hitPoints;
};
}
}

View File

@ -0,0 +1,68 @@
/*
* ace_medical.cpp
*
* Author:
* Glowbal
*/
#include "shared.hpp"
#include <vector>
#include <string>
#include <sstream>
#include "handleDamage.h"
#include "OpenWound.h"
extern "C" {
EXPORT void __stdcall RVExtension(char *output, int outputSize, const char *function);
};
std::vector<std::string> parseExtensionInput(const std::string& input)
{
std::istringstream ss(input);
std::string token;
std::vector<std::string> output;
while (std::getline(ss, token, ',')) {
output.push_back(token);
}
return output;
}
void __stdcall RVExtension(char *output, int outputSize, const char *function) {
if (!strcmp(function, "version")) {
strncpy(output, ACE_FULL_VERSION_STR, outputSize);
}
else
{
std::string returnValue = "";
std::vector<std::string> arguments = parseExtensionInput(function);
if (arguments.size() > 0)
{
std::string command = arguments.at(0);
arguments.erase(arguments.begin());
if (command == "addInjuryType") {
returnValue = ace::medical::handleDamage::GetInstance().AddInjuryType(arguments);
}
else if (command == "addDamageType") {
returnValue = ace::medical::handleDamage::GetInstance().AddDamageType(arguments);
}
else if (command == "HandleDamageWounds") {
if (arguments.size() >= 4) {
std::string selectionName = arguments.at(0);
double amountOfDamage = std::stod(arguments.at(1));
std::string typeOfDamage = arguments.at(2);
int woundID = std::stoi(arguments.at(3));
returnValue = ace::medical::handleDamage::GetInstance().HandleDamageWounds(selectionName, amountOfDamage, typeOfDamage, woundID);
}
}
else if (command == "ConfigComplete") {
ace::medical::handleDamage::GetInstance().FinalizeDefinitions();
}
}
strncpy(output, returnValue.c_str(), outputSize);
output[outputSize - 1] = '\0';
}
}