|  | @@ -29,6 +29,7 @@
 | 
	
		
			
				|  |  |  #include "../gui/CGuiHandler.h"
 | 
	
		
			
				|  |  |  #include "../gui/CursorHandler.h"
 | 
	
		
			
				|  |  |  #include "../adventureMap/CInGameConsole.h"
 | 
	
		
			
				|  |  | +#include "../client/render/CAnimation.h"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #include "../../CCallback.h"
 | 
	
		
			
				|  |  |  #include "../../lib/BattleFieldHandler.h"
 | 
	
	
		
			
				|  | @@ -36,6 +37,83 @@
 | 
	
		
			
				|  |  |  #include "../../lib/CStack.h"
 | 
	
		
			
				|  |  |  #include "../../lib/spells/ISpellMechanics.h"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +namespace HexMasks
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	// mask definitions that has set to 1 the edges present in the hex edges highlight image
 | 
	
		
			
				|  |  | +	/*
 | 
	
		
			
				|  |  | +	    /\
 | 
	
		
			
				|  |  | +	   0  1
 | 
	
		
			
				|  |  | +	  /    \
 | 
	
		
			
				|  |  | +	 |      |
 | 
	
		
			
				|  |  | +	 5      2
 | 
	
		
			
				|  |  | +	 |      |
 | 
	
		
			
				|  |  | +	  \    /
 | 
	
		
			
				|  |  | +	   4  3
 | 
	
		
			
				|  |  | +	    \/
 | 
	
		
			
				|  |  | +	*/
 | 
	
		
			
				|  |  | +	enum HexEdgeMasks {
 | 
	
		
			
				|  |  | +		empty                 = 0b000000, // empty used when wanting to keep indexes the same but no highlight should be displayed
 | 
	
		
			
				|  |  | +		topLeft               = 0b000001,
 | 
	
		
			
				|  |  | +		topRight              = 0b000010,
 | 
	
		
			
				|  |  | +		right                 = 0b000100,
 | 
	
		
			
				|  |  | +		bottomRight           = 0b001000,
 | 
	
		
			
				|  |  | +		bottomLeft            = 0b010000,
 | 
	
		
			
				|  |  | +		left                  = 0b100000,
 | 
	
		
			
				|  |  | +						  
 | 
	
		
			
				|  |  | +		top                   = 0b000011,
 | 
	
		
			
				|  |  | +		bottom                = 0b011000,
 | 
	
		
			
				|  |  | +		topRightHalfCorner    = 0b000110,
 | 
	
		
			
				|  |  | +		bottomRightHalfCorner = 0b001100,
 | 
	
		
			
				|  |  | +		bottomLeftHalfCorner  = 0b110000,
 | 
	
		
			
				|  |  | +		topLeftHalfCorner     = 0b100001,
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		rightTopAndBottom     = 0b001010, // special case, right half can be drawn instead of only top and bottom
 | 
	
		
			
				|  |  | +		leftTopAndBottom      = 0b010001, // special case, left half can be drawn instead of only top and bottom
 | 
	
		
			
				|  |  | +						  
 | 
	
		
			
				|  |  | +		rightHalf             = 0b001110,
 | 
	
		
			
				|  |  | +		leftHalf              = 0b110001,
 | 
	
		
			
				|  |  | +						  
 | 
	
		
			
				|  |  | +		topRightCorner        = 0b000111,
 | 
	
		
			
				|  |  | +		bottomRightCorner     = 0b011100,
 | 
	
		
			
				|  |  | +		bottomLeftCorner      = 0b111000,
 | 
	
		
			
				|  |  | +		topLeftCorner         = 0b100011
 | 
	
		
			
				|  |  | +	};
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +std::map<int, int> hexEdgeMaskToFrameIndex;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// Maps HexEdgesMask to "Frame" indexes for range highligt images
 | 
	
		
			
				|  |  | +void initializeHexEdgeMaskToFrameIndex()
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	hexEdgeMaskToFrameIndex[HexMasks::empty] = 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::topLeft] = 1;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::topRight] = 2;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::right] = 3;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::bottomRight] = 4;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::bottomLeft] = 5;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::left] = 6;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::top] = 7;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::bottom] = 8;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::topRightHalfCorner] = 9;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::bottomRightHalfCorner] = 10;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::bottomLeftHalfCorner] = 11;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::topLeftHalfCorner] = 12;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::rightTopAndBottom] = 13;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::leftTopAndBottom] = 14;
 | 
	
		
			
				|  |  | +	
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::rightHalf] = 13;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::leftHalf] = 14;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::topRightCorner] = 15;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::bottomRightCorner] = 16;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::bottomLeftCorner] = 17;
 | 
	
		
			
				|  |  | +    hexEdgeMaskToFrameIndex[HexMasks::topLeftCorner] = 18;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  BattleFieldController::BattleFieldController(BattleInterface & owner):
 | 
	
		
			
				|  |  |  	owner(owner)
 | 
	
		
			
				|  |  |  {
 | 
	
	
		
			
				|  | @@ -50,6 +128,12 @@ BattleFieldController::BattleFieldController(BattleInterface & owner):
 | 
	
		
			
				|  |  |  	attackCursors = std::make_shared<CAnimation>("CRCOMBAT");
 | 
	
		
			
				|  |  |  	attackCursors->preload();
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages = std::make_unique<CAnimation>("battle/rangeHighlights/rangeHighlightsGreen.json");
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->preload();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	initializeHexEdgeMaskToFrameIndex();
 | 
	
		
			
				|  |  | +	flipRangedFullDamageLimitImagesIntoPositions();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	if(!owner.siegeController)
 | 
	
		
			
				|  |  |  	{
 | 
	
		
			
				|  |  |  		auto bfieldType = owner.curInt->cb->battleGetBattlefieldType();
 | 
	
	
		
			
				|  | @@ -361,6 +445,132 @@ std::set<BattleHex> BattleFieldController::getHighlightedHexesForMovementTarget(
 | 
	
		
			
				|  |  |  	return {};
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +std::vector<BattleHex> BattleFieldController::getRangedFullDamageHexes()
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	std::vector<BattleHex> rangedFullDamageHexes; // used for return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// if not a hovered arcer unit -> return
 | 
	
		
			
				|  |  | +	auto hoveredHex = getHoveredHex();
 | 
	
		
			
				|  |  | +	const CStack * hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if (!settings["battle"]["rangedFullDamageLimitHighlightOnHover"].Bool() && !GH.isKeyboardShiftDown())
 | 
	
		
			
				|  |  | +		return rangedFullDamageHexes;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if(!(hoveredStack && hoveredStack->isShooter()))
 | 
	
		
			
				|  |  | +		return rangedFullDamageHexes;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	auto rangedFullDamageDistance = hoveredStack->getRangedFullDamageDistance();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// get only battlefield hexes that are in full range damage distance
 | 
	
		
			
				|  |  | +	std::set<BattleHex> fullRangeLimit; 
 | 
	
		
			
				|  |  | +	for(auto i = 0; i < GameConstants::BFIELD_SIZE; i++)
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		BattleHex hex(i);
 | 
	
		
			
				|  |  | +		if(hex.isAvailable() && BattleHex::getDistance(hoveredHex, hex) <= rangedFullDamageDistance)
 | 
	
		
			
				|  |  | +			rangedFullDamageHexes.push_back(hex);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	return rangedFullDamageHexes;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +std::vector<BattleHex> BattleFieldController::getRangedFullDamageLimitHexes(std::vector<BattleHex> rangedFullDamageHexes)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	std::vector<BattleHex> rangedFullDamageLimitHexes; // used for return
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// if not a hovered arcer unit -> return
 | 
	
		
			
				|  |  | +	auto hoveredHex = getHoveredHex();
 | 
	
		
			
				|  |  | +	const CStack * hoveredStack = owner.curInt->cb->battleGetStackByPos(hoveredHex, true);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if(!(hoveredStack && hoveredStack->isShooter()))
 | 
	
		
			
				|  |  | +		return rangedFullDamageLimitHexes;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	auto rangedFullDamageDistance = hoveredStack->getRangedFullDamageDistance();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	// from ranged full damage hexes get only the ones at the limit
 | 
	
		
			
				|  |  | +	for(auto & hex : rangedFullDamageHexes)
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		if(BattleHex::getDistance(hoveredHex, hex) == rangedFullDamageDistance)
 | 
	
		
			
				|  |  | +			rangedFullDamageLimitHexes.push_back(hex);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	return rangedFullDamageLimitHexes;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +std::vector<std::vector<BattleHex::EDir>> BattleFieldController::getOutsideNeighbourDirectionsForLimitHexes(std::vector<BattleHex> rangedFullDamageHexes, std::vector<BattleHex> rangedFullDamageLimitHexes)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	std::vector<std::vector<BattleHex::EDir>> output;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if(rangedFullDamageHexes.empty())
 | 
	
		
			
				|  |  | +		return output;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	for(auto & hex : rangedFullDamageLimitHexes)
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		// get all neighbours and their directions
 | 
	
		
			
				|  |  | +		
 | 
	
		
			
				|  |  | +		auto neighbouringTiles = hex.allNeighbouringTiles();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		std::vector<BattleHex::EDir> outsideNeighbourDirections;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		// for each neighbour add to output only the valid ones and only that are not found in rangedFullDamageHexes
 | 
	
		
			
				|  |  | +		for(auto direction = 0; direction < 6; direction++)
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			if(!neighbouringTiles[direction].isAvailable())
 | 
	
		
			
				|  |  | +				continue;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			auto it = std::find(rangedFullDamageHexes.begin(), rangedFullDamageHexes.end(), neighbouringTiles[direction]);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			if(it == rangedFullDamageHexes.end())
 | 
	
		
			
				|  |  | +				outsideNeighbourDirections.push_back(BattleHex::EDir(direction)); // push direction
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		output.push_back(outsideNeighbourDirections);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	return output;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +std::vector<std::shared_ptr<IImage>> BattleFieldController::calculateRangedFullDamageHighlightImages(std::vector<std::vector<BattleHex::EDir>> rangedFullDamageLimitHexesNeighbourDirections)
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	std::vector<std::shared_ptr<IImage>> output; // if no image is to be shown an empty image is still added to help with traverssing the range
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	if(rangedFullDamageLimitHexesNeighbourDirections.empty())
 | 
	
		
			
				|  |  | +		return output;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	for(auto & directions : rangedFullDamageLimitHexesNeighbourDirections)
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +		std::bitset<6> mask;
 | 
	
		
			
				|  |  | +		
 | 
	
		
			
				|  |  | +		// convert directions to mask
 | 
	
		
			
				|  |  | +		for(auto direction : directions)
 | 
	
		
			
				|  |  | +			mask.set(direction);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +		uint8_t imageKey = static_cast<uint8_t>(mask.to_ulong());
 | 
	
		
			
				|  |  | +		output.push_back(rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[imageKey]));
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	return output;
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +void BattleFieldController::flipRangedFullDamageLimitImagesIntoPositions()
 | 
	
		
			
				|  |  | +{
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRight])->verticalFlip();
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::right])->verticalFlip();
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRight])->doubleFlip();
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeft])->horizontalFlip();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottom])->horizontalFlip();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightHalfCorner])->verticalFlip();
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightHalfCorner])->doubleFlip();
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftHalfCorner])->horizontalFlip();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::rightHalf])->verticalFlip();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::topRightCorner])->verticalFlip();
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomRightCorner])->doubleFlip();
 | 
	
		
			
				|  |  | +	rangedFullDamageLimitImages->getImage(hexEdgeMaskToFrameIndex[HexMasks::bottomLeftCorner])->horizontalFlip();
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  |  	std::set<BattleHex> hoveredStackMovementRangeHexes = getMovementRangeForHoveredStack();
 | 
	
	
		
			
				|  | @@ -370,6 +580,12 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 | 
	
		
			
				|  |  |  	if(getHoveredHex() == BattleHex::INVALID)
 | 
	
		
			
				|  |  |  		return;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	// calculate array with highlight images for ranged full damage limit
 | 
	
		
			
				|  |  | +	std::vector<BattleHex> rangedFullDamageHexes = getRangedFullDamageHexes();
 | 
	
		
			
				|  |  | +	std::vector<BattleHex> rangedFullDamageLimitHexes = getRangedFullDamageLimitHexes(rangedFullDamageHexes);
 | 
	
		
			
				|  |  | +	std::vector<std::vector<BattleHex::EDir>> rangedFullDamageLimitHexesNeighbourDirections = getOutsideNeighbourDirectionsForLimitHexes(rangedFullDamageHexes, rangedFullDamageLimitHexes);
 | 
	
		
			
				|  |  | +	std::vector<std::shared_ptr<IImage>> rangedFullDamageLimitHexesHighligts = calculateRangedFullDamageHighlightImages(rangedFullDamageLimitHexesNeighbourDirections);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	auto const & hoveredMouseHexes = owner.actionsController->currentActionSpellcasting(getHoveredHex()) ? hoveredSpellHexes : hoveredMoveHexes;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	for(int hex = 0; hex < GameConstants::BFIELD_SIZE; ++hex)
 | 
	
	
		
			
				|  | @@ -377,6 +593,16 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 | 
	
		
			
				|  |  |  		bool stackMovement = hoveredStackMovementRangeHexes.count(hex);
 | 
	
		
			
				|  |  |  		bool mouse = hoveredMouseHexes.count(hex);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +		// calculate if hex is Ranged Full Damage Limit and its position in highlight array
 | 
	
		
			
				|  |  | +		bool isRangedFullDamageLimit = false;
 | 
	
		
			
				|  |  | +		int hexIndexInRangedFullDamageLimit = 0;
 | 
	
		
			
				|  |  | +		if(!rangedFullDamageLimitHexes.empty())
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			auto pos = std::find(rangedFullDamageLimitHexes.begin(), rangedFullDamageLimitHexes.end(), hex);
 | 
	
		
			
				|  |  | +			hexIndexInRangedFullDamageLimit = std::distance(rangedFullDamageLimitHexes.begin(), pos);
 | 
	
		
			
				|  |  | +			isRangedFullDamageLimit = pos != rangedFullDamageLimitHexes.end();
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  		if(stackMovement && mouse) // area where hovered stackMovement can move shown with highlight. Because also affected by mouse cursor, shade as well
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  |  			showHighlightedHex(canvas, cellUnitMovementHighlight, hex, false);
 | 
	
	
		
			
				|  | @@ -390,6 +616,10 @@ void BattleFieldController::showHighlightedHexes(Canvas & canvas)
 | 
	
		
			
				|  |  |  		{
 | 
	
		
			
				|  |  |  			showHighlightedHex(canvas, cellUnitMovementHighlight, hex, false);
 | 
	
		
			
				|  |  |  		}
 | 
	
		
			
				|  |  | +		if(isRangedFullDamageLimit)
 | 
	
		
			
				|  |  | +		{
 | 
	
		
			
				|  |  | +			showHighlightedHex(canvas, rangedFullDamageLimitHexesHighligts[hexIndexInRangedFullDamageLimit], hex, false);
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 |