Home » MODDING HQ 1.13 » v1.13 Coding Talk » Code Snippets  () 1 Vote
Re: Code Snippets[message #358608 is a reply to message #358318] Fri, 06 December 2019 14:34 Go to previous messageGo to next message
Deleted.

 
Messages:2657
Registered:December 2012
Location: Russian Federation
Need help from experienced 1.13 coders!
I am trying to implement top bar auto hiding feature, it should look like this:

The code is simple, in Interface.cpp, we define callback function
bool bHideTopMessage = false;

void TopMessageRegionCallback(MOUSE_REGION * pRegion, INT32 iReason)
{
	if (!bHideTopMessage && 
		gTacticalStatus.fInTopMessage &&
		iReason & (MSYS_CALLBACK_REASON_GAIN_MOUSE | MSYS_CALLBACK_REASON_MOVE))
	{
		bHideTopMessage = true;
		EndTopMessage();
		return;
	}
	if (bHideTopMessage && iReason & MSYS_CALLBACK_REASON_LOST_MOUSE)
	{
		gTacticalStatus.fInTopMessage = TRUE;
		bHideTopMessage = false;
		return;
	}
}
And in InitializeTacticalInterface() we define mouse region:
MSYS_DefineRegion(&gTopMessageRegion, 0, 0, SCREEN_WIDTH, HEIGHT_PROGRESSBAR, MSYS_PRIORITY_HIGHEST, 0, TopMessageRegionCallback, NULL);
MSYS_AddRegion(&gTopMessageRegion);
Now the top bar is auto removed when mouse is in the top bar region, showing top part of the map.
(the code is from old Ja2'005 mod by Bugmonster).

The problem is - since we catch mouse event, other functions in this area stop working - there is no traverse north cursor, no attack cursor in this area etc.
As I understand it, we should send mouse event further to standard code, but I have no idea where to find it.
Also, if the region is created, it should be destroyed later somewhere.

If you are interested in making this feature working and have some ideas, please share here!



Left this community.

Report message to a moderator

Lieutenant

Re: Code Snippets[message #358640 is a reply to message #358608] Thu, 12 December 2019 05:54 Go to previous messageGo to next message
Deleted.

 
Messages:2657
Registered:December 2012
Location: Russian Federation
Soldier Control.cpp, GetAPBonus()
UINT8 ubSector = (UINT8)SECTOR( this->sSectorX, this->sSectorY );
UINT8 ubTraverseType = SectorInfo[ubSector].ubTraversability[ubDirection];

switch ( ubTraverseType )
{
case NS_RIVER:
case EW_RIVER:
	bonus += this->GetBackgroundValue( BG_RIVER );
	break;
...
Looks like sector type AP bonus from background depends on the direction the soldier is looking currently.
Shouldn't it be
ubTraversability[THROUGH_STRATEGIC_MOVE]
or something like that?



Left this community.

Report message to a moderator

Lieutenant

Re: Code Snippets[message #358642 is a reply to message #358640] Thu, 12 December 2019 08:13 Go to previous messageGo to next message
Deleted.

 
Messages:2657
Registered:December 2012
Location: Russian Federation
LOS.cpp, CalcPreRecoilOffset(), CalcRecoilOffset()
FLOAT dCounterForceMax = CalcCounterForceMax(pShooter, pWeapon);

UINT8 stance = gAnimControl[ pShooter->usAnimState ].ubEndHeight;

// Flugente: new feature: if the next tile in our sight direction has a height so that we could rest our weapon on it, we do that, thereby gaining the prone boni instead. This includes bipods
if ( gGameExternalOptions.fWeaponResting && pShooter->IsWeaponMounted() )
	stance = ANIM_PRONE;

FLOAT moda = CalcCounterForceMax(pShooter, pWeapon, stance);
FLOAT modb = CalcCounterForceMax(pShooter, pWeapon, gAnimControl[ pShooter->usAnimState ].ubEndHeight);
FLOAT iCounterForceMax = ((gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb)/100);
iCounterForceMax is used to take into account weapon resting, but it's never used in the code after that, the code uses only old dCounterForceMax value.



Left this community.

Report message to a moderator

Lieutenant

Re: Code Snippets[message #358647 is a reply to message #358642] Fri, 13 December 2019 15:37 Go to previous messageGo to next message
Deleted.

 
Messages:2657
Registered:December 2012
Location: Russian Federation
Interface Items.cpp, HandleItemPointerClick()
// Given our gridno, throw grenate!
CalculateLaunchItemParamsForThrow( gpItemPointerSoldier, sGridNo, gpItemPointerSoldier->pathing.bLevel, (INT16)( ( gsInterfaceLevel * 256 ) + sEndZ ), gpItemPointer, 100, ubThrowActionCode, uiThrowActionData, gpItemPointer->usItem );
I think it should be
CalculateLaunchItemParamsForThrow( gpItemPointerSoldier, sGridNo, gsInterfaceLevel , (INT16)( ( gsInterfaceLevel * 256 ) + sEndZ ), gpItemPointer, 100, ubThrowActionCode, uiThrowActionData, gpItemPointer->usItem );
because ubLevel param in CalculateLaunchItemParamsForThrow() is the level of target, not attacker.



Left this community.

Report message to a moderator

Lieutenant

Re: Code Snippets[message #358653 is a reply to message #358647] Sat, 14 December 2019 14:02 Go to previous messageGo to next message
silversurfer

 
Messages:2791
Registered:May 2009
Thank you for finding all these little bugs in the code. I think the counter force line should be something like this:

dCounterForceMax = ((gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb)/100);
We have no need for another variable here.

Maybe you could implement the fixes described here as well? I can't debug the game anymore with VS2019 because of bugs with the game's vector code. You seem to be using a version of VS that still works.



Wildfire Maps Mod 6.07 on SVN: https://ja2svn.mooo.com/source/ja2/branches/Wanne/JA2%201.13%20Wildfire%206.06%20-%20Maps%20MOD

Report message to a moderator

Lieutenant
Re: Code Snippets[message #358654 is a reply to message #358653] Sat, 14 December 2019 14:16 Go to previous messageGo to next message
Deleted.

 
Messages:2657
Registered:December 2012
Location: Russian Federation
silversurfer wrote on Sat, 14 December 2019 17:02
Maybe you could implement the fixes described here as well? I can't debug the game anymore with VS2019 because of bugs with the game's vector code. You seem to be using a version of VS that still works.
These fixes by merc05 are already in +AI code (at least those applicable to 7609 version), seem to work good so far, at least not causing any problems.
I think for simple fixes you can work with release version, and it's much faster. I usually compile into debug only when searching for some really nasty bug.
For me, working with the main trunk has a different problem - I hate new cheat code change and removed options from start page, it's extremely inconvenient, killing any motivation to even run this version.



Left this community.

Report message to a moderator

Lieutenant

Re: Code Snippets[message #359935 is a reply to message #358654] Wed, 06 May 2020 00:36 Go to previous messageGo to next message
Deleted.

 
Messages:2657
Registered:December 2012
Location: Russian Federation
Something I don't understand.

Interface Items.cpp, UpdateAttachmentTooltips()
//Print all attachments that fit on this item.
for(UINT16 usLoop = 0; usLoop < MAXATTACHMENTS; ++usLoop)
{	//We no longer find valid attachments from AttachmentSlots.xml so we need to work a bit harder to get our list
	usAttachment = 0;
	//Madd: Common Attachment Framework
	if (Item[usLoop].nasAttachmentClass & AttachmentSlots[usLoopSlotID].nasAttachmentClass && IsAttachmentPointAvailable(point, usLoop, TRUE))
	{
		usAttachment = usLoop;
		if( !Item[usAttachment].hiddenaddon && !Item[usAttachment].hiddenattachment && ItemIsLegal(usAttachment))
		{
			bool exists = false;
			for (UINT32 i = 0; i < attachList.size(); i++)
			{
				if ( attachList[i] == usAttachment )
				{
					exists = true;
					break;
				}
			}
			if (!exists)
				attachList.push_back(usAttachment);
		}
	}
	usAttachment = 0;
	if(Attachment[usLoop][1] == pObject->usItem && AttachmentSlots[usLoopSlotID].nasAttachmentClass & Item[Attachment[usLoop][0]].nasAttachmentClass)
	{	//search primary item attachments.xml
		usAttachment = Attachment[usLoop][0];
	}
	else if(Launchable[usLoop][1] == pObject->usItem && AttachmentSlots[usLoopSlotID].nasAttachmentClass & Item[Launchable[usLoop][0]].nasAttachmentClass)
	{	//search primary item launchables.xml
		usAttachment = Launchable[usLoop][0];
	}
...
Why usLoop is used both for Item[usLoop] and Attachment[usLoop] at the same time? These are different arrays, as I understand.
Also, max value for usLoop is MAXATTACHMENTS, while Item is defined as INVTYPE Item[MAXITEMS], where MAXITEMS is less than MAXATTACHMENTS, so this could potentially result in accessing memory outside of Item[] bounds.
How is all this supposed to work?

[Updated on: Wed, 06 May 2020 01:32]




Left this community.

Report message to a moderator

Lieutenant

Re: Code Snippets[message #360769 is a reply to message #359935] Fri, 31 July 2020 12:15 Go to previous messageGo to next message
Deleted.

 
Messages:2657
Registered:December 2012
Location: Russian Federation
Looking at Attacks.cpp

void UnderFire::Add(UINT16 usID, UINT8 ubCTH)
{
	if(!fEnable)
		return;

	if(usUnderFireCnt < MAXUNDERFIRE)
	{
		for(int i=0; i<usUnderFireCnt; i++)
			if(usUnderFireID[i] == usID)
			{
				if(ubUnderFireCTH[i] != ubCTH)
					ubUnderFireCTH[i] = ubCTH;
				return;
			}

		usUnderFireID[usUnderFireCnt++] = usID;
	}
}

When game detects possible bullet impact into person, it calls this function, to store possible friendly fire chance:
LOS.cpp
gUnderFire.Add(pStructure->usStructureID, 100);
or
gUnderFire.Add(pStructure->usStructureID, MIN_CHANCE_TO_ACCIDENTALLY_HIT_SOMEONE);

Now, looking at the UnderFire::Add() function, it stores usID for new person, but it never stores ubCTH if the bullet hits this person for the first time during test shot.
And it's probably never called again after that for that person, so for any soldier it will return friendly fire chance = 0, even though it can count possible hits correctly.
As a result, the game never cares about hitting someone because friendly CTH is always 0.

Changing code to
void UnderFire::Add(UINT16 usID, UINT8 ubCTH)
{
	if (!fEnable)
		return;
	if (usUnderFireCnt < MAXUNDERFIRE)
	{
		for (int i = 0; i < usUnderFireCnt; i++)
		{
			if (usUnderFireID[i] == usID)
			{
				if (ubUnderFireCTH[i] != ubCTH)
					ubUnderFireCTH[i] = ubCTH;
				return;
			}
		}
		ubUnderFireCTH[usUnderFireCnt] = ubCTH;
		usUnderFireID[usUnderFireCnt] = usID;
		usUnderFireCnt++;
	}
}
fixes the problem.

I plan to fix this issue soon in the trunk.



Left this community.

Report message to a moderator

Lieutenant

Re: Code Snippets[message #362288 is a reply to message #172712] Sun, 31 January 2021 09:16 Go to previous messageGo to next message
rftr is currently offline rftr

 
Messages:32
Registered:August 2020
Hello!

I've made a small change to stop Speck from spamming your email when multiple new mercs are available on the same day.

void NewMercsAvailableAtMercSiteCallBack( )
{
	// rftr: don't spam the user's email when MERC has multiple new personnel available on the same day
	bool sentNewMercsEmail = false;

	for(UINT8 i=0; i<NUM_PROFILES; i++)
	{			
		if ( gConditionsForMercAvailability[i].ProfilId != 0 && gConditionsForMercAvailability[i].NewMercsAvailable == FALSE && gConditionsForMercAvailability[i].StartMercsAvailable == FALSE )
		{
			if( CanMercBeAvailableYet( gConditionsForMercAvailability[i].uiIndex ) )
			{
				gConditionsForMercAvailability[i].NewMercsAvailable = TRUE;
			
				//if ( gConditionsForMercAvailability[ gConditionsForMercAvailability[i].uiIndex ].Drunk == TRUE )
				if ( gConditionsForMercAvailability[i].Drunk == TRUE )
				{
					LaptopSaveInfo.gubLastMercIndex = gConditionsForMercAvailability[gConditionsForMercAvailability[i].uiAlternateIndex].uiIndex;
				}
				else
				{
					// If Previous merc has alternate index
					if (i > 0 && gConditionsForMercAvailability[ gConditionsForMercAvailability[i - 1].uiIndex ].uiAlternateIndex != 255)					
					{
						// Previous merc has alternate (drunk) merc, skip his one!						
						//LaptopSaveInfo.gubLastMercIndex = LaptopSaveInfo.gubLastMercIndex + 2;
						LaptopSaveInfo.gubLastMercIndex = LaptopSaveInfo.gubLastMercIndex++;
					}
					else
					{
						LaptopSaveInfo.gubLastMercIndex++;
					}
				}
				//gConditionsForMercAvailability[gConditionsForMercAvailability[i].uiIndex].NewMercsAvailable = TRUE;				LaptopSaveInfo.ubLastMercAvailableId = gConditionsForMercAvailability[i].uiIndex;
				gConditionsForMercAvailability[i].StartMercsAvailable = TRUE;

#ifdef JA2UB
				if (!sentNewMercsEmail)
				{
					sentNewMercsEmail = true;
					AddEmail( NEW_MERCS_AT_MERC, NEW_MERCS_AT_MERC_LENGTH, SPECK_FROM_MERC, GetWorldTotalMin(), -1, -1, TYPE_EMAIL_EMAIL_EDT);
				}

				//new mercs are available
				LaptopSaveInfo.fNewMercsAvailableAtMercSite = TRUE;
#else
				if( IsSpeckComAvailable() )
				{
					if (!sentNewMercsEmail)
					{
						sentNewMercsEmail = true;
						AddEmail( NEW_MERCS_AT_MERC, NEW_MERCS_AT_MERC_LENGTH, SPECK_FROM_MERC, GetWorldTotalMin(), -1, -1, TYPE_EMAIL_EMAIL_EDT);
					}

					//new mercs are available
					LaptopSaveInfo.fNewMercsAvailableAtMercSite = TRUE;
				}
				else
				{
					// anv: Have speck inform player personally
					TacticalCharacterDialogue( FindSoldierByProfileID( SPECK_PLAYABLE, TRUE ), SPECK_PLAYABLE_QUOTE_NEW_MERCS_AVAILABLE );
				}
#endif
			}
		}
	}
}

Report message to a moderator

Private 1st Class
Re: Code Snippets[message #362289 is a reply to message #172712] Sun, 31 January 2021 09:46 Go to previous messageGo to next message
rftr is currently offline rftr

 
Messages:32
Registered:August 2020
This is a quality-of-life change when shopping around for LBE at Bobby Ray's to show some additional stats for LBE (MOLLE space available/consumed, MOLLE slots, combat pack/backpack combos).

In BobbyRGuns.cpp:
void GetHelpTextForItemInLaptop( STR16 pzStr, UINT16 usItemNumber )
{		
	CHAR16	zItemName[ SIZE_ITEM_NAME ];
	UINT8	ubItemCount=0;

	//get item weight
	FLOAT fWeight = GetWeightBasedOnMetricOption(Item[ usItemNumber ].ubWeight)/10;

	switch( Item[ usItemNumber ].usItemClass )
	{
...

	// rftr: better LBE tooltips
	case IC_LBEGEAR:
		{
			CHAR16 lbeStr[1000];
			CHAR16 temp[100];

			swprintf(lbeStr, L"");
			swprintf(temp, L"");

			if (UsingNewInventorySystem())
			{
				INVTYPE item = Item[usItemNumber];
				LBETYPE lbe = LoadBearingEquipment[item.ubClassIndex];

				if (lbe.lbeAvailableVolume > 0) // if this item is a MOLLE carrier (thigh rig, vest, combat pack, backpack)
				{
					swprintf(temp, L"\n \n%s\n%s %d", gLbeStatsDesc[6+lbe.lbeClass], gLbeStatsDesc[0], lbe.lbeAvailableVolume);
					wcscat(lbeStr, temp);

					UINT8 molleSmallCount = 0;
					UINT8 molleMediumCount = 0;
					
					for (UINT32 sCount = 1; sCount < gMAXITEMS_READ; ++sCount)
					{
						AttachmentSlotStruct attachmentSlot = AttachmentSlots[sCount];
						if (attachmentSlot.uiSlotIndex == 0)
							break;

						// count MOLLE slots for this carrier
						if (attachmentSlot.nasLayoutClass == (1 << (lbe.lbeClass + 1)))
						{
							if (lbe.lbePocketsAvailable & (1 << (attachmentSlot.ubPocketMapping - 1)))
							{
								// seems to be equivalent to checking attachmentSlot.fBigSlot
								if (attachmentSlot.nasAttachmentClass == MOLLE_SMALL)
								{
									molleSmallCount++;
								}
								else if (attachmentSlot.nasAttachmentClass == MOLLE_MEDIUM)
								{
									molleMediumCount++;
								}
							}
						}
					}
					
					if (molleSmallCount > 0)
					{
						swprintf(temp, L"\n%s %d", gLbeStatsDesc[2], molleSmallCount);
						wcscat(lbeStr, temp);
					}

					if (molleMediumCount> 0)
					{
						swprintf(temp, L"\n%s %d", gLbeStatsDesc[3], molleMediumCount);
						wcscat(lbeStr, temp);
					}
				}
				else if (item.nasAttachmentClass == MOLLE_SMALL)
				{
					swprintf(temp, L"\n \n%s\n%s\n%s %d", gLbeStatsDesc[11], gLbeStatsDesc[4], gLbeStatsDesc[1], LBEPocketType[GetFirstPocketOnItem(usItemNumber)].pVolume);
					wcscat(lbeStr, temp);
				}
				else if (item.nasAttachmentClass == MOLLE_MEDIUM)
				{
					// special case for hydration pack (see AttachmentPoint.xml)
					swprintf(temp, L"\n \n%s\n%s\n%s %d", gLbeStatsDesc[11], item.ulAttachmentPoint == 4 ? gLbeStatsDesc[6] : gLbeStatsDesc[5], gLbeStatsDesc[1], LBEPocketType[GetFirstPocketOnItem(usItemNumber)].pVolume);
					wcscat(lbeStr, temp);
				}
				else // non-MOLLE LBE
				{
					swprintf(temp, L"\n \n%s", gLbeStatsDesc[6 + lbe.lbeClass]);
					wcscat(lbeStr, temp);
				}

				// check for combat pack/backpack combos
				if (lbe.lbeCombo > 0)
				{
					bool foundCombo = false;
					CHAR16 lbeComboStr[1000];
					swprintf(lbeComboStr, L"");
					for (UINT32 itemIndex = 0; itemIndex < gMAXITEMS_READ; ++itemIndex)
					{
						INVTYPE otherItem = Item[itemIndex];

						if (otherItem.usItemClass != IC_LBEGEAR)
							continue;

						if (itemIndex == usItemNumber)
							continue;

						LBETYPE otherLbe = LoadBearingEquipment[otherItem.ubClassIndex];

						if (lbe.lbeClass == otherLbe.lbeClass)
							continue;

						if (lbe.lbeCombo == otherLbe.lbeCombo)
						{
							foundCombo = true;
							swprintf(temp, L"\n%s", otherItem.szBRName);
							wcscat(lbeComboStr, temp);
						}
					}

					if (foundCombo)
					{
						// only combat packs and backpacks will have lbeCombo
						swprintf(temp, L"\n \n%s\n%s", gLbeStatsDesc[lbe.lbeClass == COMBAT_PACK ? 12 : 13], lbeComboStr);
						wcscat(lbeStr, temp);
					}
				}
			}

			swprintf( pzStr, L"%s\n%s %1.1f %s%s",
				ItemNames[ usItemNumber ],	//Item long name
				gWeaponStatsDesc[ 12 ],			//Weight String
				fWeight,						//Weight
				GetWeightUnitString(),			//Weight units
				lbeStr
				);
		}
		break;

...

New defs in Item Types.h (seemed like an appropriate place to put this?):
// rftr: better LBE tooltips. see NasAttachmentClass.xml
#define MOLLE_SMALL 4096
#define MOLLE_MEDIUM 8192

And some new text in the appropriate language .cpp files (_ChineseText, _DutchText, _EnglishText, _FrenchText, _GermanText, _ItalianText) (translations TODO)
// rftr: better lbe tooltips
STR16 gLbeStatsDesc[14] =
{
	L"MOLLE Space Available:",
	L"MOLLE Space Required:",
	L"MOLLE Small Slot Count:",
	L"MOLLE Medium Slot Count:",
	L"MOLLE Pouch Size: Small",
	L"MOLLE Pouch Size: Medium",
	L"MOLLE Pouch Size: Medium (Hydration)",
	L"Thigh Rig",
	L"Vest",
	L"Combat Pack",
	L"Backpack", // 10
	L"MOLLE Pouch",
	L"This combat pack can be worn with the following backpacks:",
	L"This backpack can be worn with the following combat packs:",

};

And in Text.h:
// rftr: better LBE tooltips
extern STR16		gLbeStatsDesc[14];

Report message to a moderator

Private 1st Class
Re: Code Snippets[message #362292 is a reply to message #362289] Sun, 31 January 2021 14:38 Go to previous messageGo to next message
Deleted.

 
Messages:2657
Registered:December 2012
Location: Russian Federation
@rftr
Tried to implement your BR LBE tooltips in 7609+AI, but the code crashes with some invalid access exception when trying to open one of the Igor's kits in hiring page (using WF+SDO from my modpack).
Not sure if it's because of 7609 incompatibility or some problem with the code.



Left this community.

Report message to a moderator

Lieutenant

Re: Code Snippets[message #362293 is a reply to message #362292] Sun, 31 January 2021 17:39 Go to previous messageGo to next message
rftr is currently offline rftr

 
Messages:32
Registered:August 2020
It's likely 7609 incompatibility as I've only tried on the latest trunk.

Report message to a moderator

Private 1st Class
Re: Code Snippets[message #362295 is a reply to message #362293] Sun, 31 January 2021 20:25 Go to previous messageGo to next message
Deleted.

 
Messages:2657
Registered:December 2012
Location: Russian Federation
Ok, looking at the code:
// only combat packs and backpacks will have lbeCombo
swprintf(temp, L"\n \n%s\n%s", gLbeStatsDesc[lbe.lbeClass == COMBAT_PACK ? 12 : 13], lbeComboStr);
temp has max size 100 while lbeComboStr has size 1000
CHAR16 temp[100];
...
CHAR16 lbeComboStr[1000];
which causes a problem when the code is used with mod like SDO which defines dozens of compatible combat packs and backpacks.
temp should have size at least the same as lbeComboStr, since you copy one into another:
CHAR16 temp[1000];
It would also be a good idea to limit lbeComboStr growing in case of using some extreme mod like Aimnas:
if (wcslen(lbeComboStr) < 800)
	wcscat(lbeComboStr, temp);
else
	wcscat(lbeComboStr, L"\n...");
so that there is still some place for other text.

With the mentioned fixes, the code works well in 7609.

Also, compiler shows warning:
BobbyRGuns.cpp(4367): warning C4334: '<<' : result of 32-bit shift implicitly converted to 64 bits (was 64-bit shift intended?)
which would be good to have fixed before adding this to the trunk.

Another suggestion is to shorten last two description lines to:
L"Compatible backpacks:",
L"Compatible combat packs:",

[Updated on: Sun, 31 January 2021 20:29]




Left this community.

Report message to a moderator

Lieutenant

Re: Code Snippets[message #362296 is a reply to message #362295] Sun, 31 January 2021 21:42 Go to previous messageGo to next message
rftr is currently offline rftr

 
Messages:32
Registered:August 2020
Thanks for the catch, seven. I'm not running any mods so I couldn't test compatibility in those cases :(

Here's the updated code in BobbyRGuns.cpp:
	// rftr: better LBE tooltips
	case IC_LBEGEAR:
		{
			CHAR16 lbeStr[1000];
			CHAR16 temp[1000];

			swprintf(lbeStr, L"");
			swprintf(temp, L"");

			if (UsingNewInventorySystem())
			{
				INVTYPE item = Item[usItemNumber];
				LBETYPE lbe = LoadBearingEquipment[item.ubClassIndex];

				if (lbe.lbeAvailableVolume > 0) // if this item is a MOLLE carrier (thigh rig, vest, combat pack, backpack)
				{
					swprintf(temp, L"\n \n%s\n%s %d", gLbeStatsDesc[6+lbe.lbeClass], gLbeStatsDesc[0], lbe.lbeAvailableVolume);
					wcscat(lbeStr, temp);

					UINT8 molleSmallCount = 0;
					UINT8 molleMediumCount = 0;
					
					for (UINT32 sCount = 1; sCount < gMAXITEMS_READ; ++sCount)
					{
						AttachmentSlotStruct attachmentSlot = AttachmentSlots[sCount];
						if (attachmentSlot.uiSlotIndex == 0)
							break;

						// count MOLLE slots for this carrier
						if (attachmentSlot.nasLayoutClass == ((UINT64)1 << (lbe.lbeClass + 1)))
						{
							if (lbe.lbePocketsAvailable & (1 << (attachmentSlot.ubPocketMapping - 1)))
							{
								// seems to be equivalent to checking attachmentSlot.fBigSlot
								if (attachmentSlot.nasAttachmentClass == MOLLE_SMALL)
								{
									molleSmallCount++;
								}
								else if (attachmentSlot.nasAttachmentClass == MOLLE_MEDIUM)
								{
									molleMediumCount++;
								}
							}
						}
					}
					
					if (molleSmallCount > 0)
					{
						swprintf(temp, L"\n%s %d", gLbeStatsDesc[2], molleSmallCount);
						wcscat(lbeStr, temp);
					}

					if (molleMediumCount> 0)
					{
						swprintf(temp, L"\n%s %d", gLbeStatsDesc[3], molleMediumCount);
						wcscat(lbeStr, temp);
					}
				}
				else if (item.nasAttachmentClass == MOLLE_SMALL)
				{
					swprintf(temp, L"\n \n%s\n%s\n%s %d", gLbeStatsDesc[11], gLbeStatsDesc[4], gLbeStatsDesc[1], LBEPocketType[GetFirstPocketOnItem(usItemNumber)].pVolume);
					wcscat(lbeStr, temp);
				}
				else if (item.nasAttachmentClass == MOLLE_MEDIUM)
				{
					// special case for hydration pack (see AttachmentPoint.xml)
					swprintf(temp, L"\n \n%s\n%s\n%s %d", gLbeStatsDesc[11], item.ulAttachmentPoint == 4 ? gLbeStatsDesc[6] : gLbeStatsDesc[5], gLbeStatsDesc[1], LBEPocketType[GetFirstPocketOnItem(usItemNumber)].pVolume);
					wcscat(lbeStr, temp);
				}
				else // non-MOLLE LBE
				{
					swprintf(temp, L"\n \n%s", gLbeStatsDesc[6 + lbe.lbeClass]);
					wcscat(lbeStr, temp);
				}

				// check for combat pack/backpack combos
				if (lbe.lbeCombo > 0)
				{
					bool foundCombo = false;
					CHAR16 lbeComboStr[1000];
					swprintf(lbeComboStr, L"");
					for (UINT32 itemIndex = 0; itemIndex < gMAXITEMS_READ; ++itemIndex)
					{
						INVTYPE otherItem = Item[itemIndex];

						if (otherItem.usItemClass != IC_LBEGEAR)
							continue;

						if (itemIndex == usItemNumber)
							continue;

						LBETYPE otherLbe = LoadBearingEquipment[otherItem.ubClassIndex];

						if (lbe.lbeClass == otherLbe.lbeClass)
							continue;

						if (lbe.lbeCombo == otherLbe.lbeCombo)
						{
							foundCombo = true;
							if (wcslen(lbeComboStr) < 800 && wcslen(otherItem.szBRName) < 100)
							{
								swprintf(temp, L"\n%s", otherItem.szBRName);
								wcscat(lbeComboStr, temp);
							}
							else
							{
								wcscat(lbeComboStr, L"\n...");
								break;
							}
						}
					}

					if (foundCombo)
					{
						// only combat packs and backpacks will have lbeCombo
						swprintf(temp, L"\n \n%s\n%s", gLbeStatsDesc[lbe.lbeClass == COMBAT_PACK ? 12 : 13], lbeComboStr);
						wcscat(lbeStr, temp);
					}
				}
			}

			swprintf( pzStr, L"%s\n%s %1.1f %s%s",
				ItemNames[ usItemNumber ],	//Item long name
				gWeaponStatsDesc[ 12 ],			//Weight String
				fWeight,						//Weight
				GetWeightUnitString(),			//Weight units
				lbeStr
				);
		}
		break;

I've used your suggestion of 800 chars for cutting off lbeComboStr, but it's also fine if you want to reduce it even more.

Can I leave you to update the two description lines before submitting to the trunk?

Report message to a moderator

Private 1st Class
Re: Code Snippets[message #362448 is a reply to message #362296] Sat, 20 February 2021 08:52 Go to previous messageGo to next message
rftr is currently offline rftr

 
Messages:32
Registered:August 2020
Hi, I've got another change. At risk of this spiraling into Yet Another Merc Randomizer, I've got a change for mercs/NPCs to draw from a skill trait pool instead of always having either set skill traits or totally random ones. Right now, the number of skill traits granted is their 1.13 default with a 50/50 chance of getting one more (100% chance for level 7+ mercs).

Soldier Profile.h:
typedef struct
{
	UINT16 uiIndex;
	BOOLEAN Enabled;
	INT8 BaseAttribute;
	BOOLEAN RandomExpLevel;
	BOOLEAN RandomLife;
	BOOLEAN RandomAgility;
	BOOLEAN RandomDexterity;
	BOOLEAN RandomStrength;
	BOOLEAN RandomLeadership;
	BOOLEAN RandomWisdom;
	BOOLEAN RandomMarksmanship;
	BOOLEAN RandomMechanical;
	BOOLEAN RandomExplosive;
	BOOLEAN RandomMedical;
	BOOLEAN RandomScientific;
	// rftr: random trait pools
	std::vector<UINT8> RandomTraitPool;
} RANDOM_STATS_VALUES;

Soldier Profile.cpp:
//Random Stats 
RANDOM_STATS_VALUES gRandomStatsValue[NUM_PROFILES];
void RandomStats();
void RandomSkillTraits(); // rftr
void RandomStartSalary();
I've put the following function after RandomStats() for consistency.
// rftr: random trait pools
void RandomSkillTraits()
{
	UINT32 cnt;
	MERCPROFILESTRUCT * pProfile;

	if (gGameExternalOptions.fMercRandomSkillTraitPools == FALSE)
		return;

	for (cnt = 0; cnt < NUM_PROFILES; cnt++)
	{
		pProfile = &(gMercProfiles[cnt]);
		const RANDOM_STATS_VALUES values = gRandomStatsValue[cnt];
		if (values.Enabled == TRUE)
		{
			std::vector<UINT8> traitPool = values.RandomTraitPool;

			// shuffle traits
			for (int a = 0; a < traitPool.size(); ++a)
			{
				const int randomIndex = Random(traitPool.size());
				std::swap(traitPool[a], traitPool[randomIndex]);
			}

			int traitCount = 0;
			for (int a = 0; a < (sizeof(pProfile->bSkillTraits) / sizeof(pProfile->bSkillTraits[0])); ++a)
			{
				if (pProfile->bSkillTraits[a] != 0)
					traitCount++;
			}

			int traitsToKeep = traitCount + (pProfile->bExpLevel >= 7 ? 1 : Random(2));
			traitsToKeep = min(traitsToKeep, traitPool.size());

			// copy over skill traits
			std::vector<UINT8>::const_iterator iter = traitPool.cbegin();
			for (int traitIndex = 0; traitIndex < (sizeof(pProfile->bSkillTraits) / sizeof(pProfile->bSkillTraits[0])); ++traitIndex)
			{
				if (traitIndex < traitsToKeep && iter != traitPool.cend())
				{
					pProfile->bSkillTraits[traitIndex] = *iter;
					iter++;
				}
				else
				{
					pProfile->bSkillTraits[traitIndex] = 0;
				}
			}
		}
	}
}
And I'm calling it immediately after RandomStats():
BOOLEAN LoadMercProfiles(void)
{
<snip>
	RandomStats (); //random stats by Jazz

	// rftr: random trait pools
	RandomSkillTraits();
	
	// Buggler: random starting salary
	RandomStartSalary ();

XML_RandomStats.cpp:
static void XMLCALL
RandomStatsStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts)
{
<snip>
		else if(pData->curElement == ELEMENT &&
			   ( strcmp(name, "uiIndex") == 0 ||
			     strcmp(name, "Enabled") == 0 ||
				 strcmp(name, "BaseAttribute") == 0 ||
				 strcmp(name, "RandomExpLevel") == 0 ||
				 strcmp(name, "RandomLife") == 0 ||
				 strcmp(name, "RandomAgility") == 0 ||
 				 strcmp(name, "RandomDexterity") == 0 ||
 				 strcmp(name, "RandomStrength") == 0 ||
				 strcmp(name, "RandomLeadership") == 0 ||
				 strcmp(name, "RandomWisdom") == 0 ||
				 strcmp(name, "RandomMarksmanship") == 0 ||
				 strcmp(name, "RandomMechanical") == 0 ||
				 strcmp(name, "RandomExplosive") == 0 ||
 				 strcmp(name, "RandomMedical") == 0 ||
				 strcmp(name, "RandomScientific") == 0 ) ||
				 // rftr: random trait pools
				 strncmp(name, "RandomSkillTraitPool", 20) == 0
			)
<snip>
}
static void XMLCALL
RandomStatsEndElementHandle(void *userData, const XML_Char *name)
{
<snip>
					gRandomStatsValue[pData->curRandomStats.uiIndex].Enabled = pData->curRandomStats.Enabled;
					gRandomStatsValue[pData->curRandomStats.uiIndex].BaseAttribute = pData->curRandomStats.BaseAttribute;
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomExpLevel = pData->curRandomStats.RandomExpLevel;
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomLife = pData->curRandomStats.RandomLife;
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomAgility = pData->curRandomStats.RandomAgility;
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomDexterity = pData->curRandomStats.RandomDexterity;
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomStrength = pData->curRandomStats.RandomStrength;
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomLeadership = pData->curRandomStats.RandomLeadership;
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomWisdom = pData->curRandomStats.RandomWisdom;	
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomMarksmanship = pData->curRandomStats.RandomMarksmanship;
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomMechanical = pData->curRandomStats.RandomMechanical;
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomExplosive = pData->curRandomStats.RandomExplosive;
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomMedical = pData->curRandomStats.RandomMedical;
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomScientific = pData->curRandomStats.RandomScientific;
					// rftr: random trait pools
					gRandomStatsValue[pData->curRandomStats.uiIndex].RandomTraitPool = pData->curRandomStats.RandomTraitPool;
					pData->curRandomStats.RandomTraitPool.clear();
		}
<snipping else-ifs>
		// rftr: random trait pools
		else if (strncmp(name, "RandomSkillTraitPool", 20) == 0)
		{
			pData->curElement = ELEMENT;
			pData->curRandomStats.RandomTraitPool.push_back((UINT8) atol(pData->szCharData));
		}

		pData->maxReadDepth--;
	}
	pData->currentDepth--;
}

GameSettings.h in the GAME_EXTERNAL_OPTIONS struct:
	BOOLEAN fMercRandomSkillTraitPools; //rftr

GameSettings.cpp, placed alongside the other merc randomizer configs:
void LoadGameExternalOptions()
{
<snip>
	gGameExternalOptions.ubMercRandomStats					= iniReader.ReadInteger("Recruitment Settings", "MERCS_RANDOM_STATS", 0, 0, 4);
	gGameExternalOptions.fMercRandomGearKits				= iniReader.ReadBoolean("Recruitment Settings", "MERCS_RANDOM_GEAR_KITS", FALSE); //Jenilee
	gGameExternalOptions.fMercRandomBellDistribution		= iniReader.ReadBoolean("Recruitment Settings", "MERCS_RANDOM_BELL_DISTRIBUTION", TRUE);
	gGameExternalOptions.ubMercRandomStatsRange				= iniReader.ReadInteger("Recruitment Settings", "MERCS_RANDOM_STAT_RANGE", 10, 0, 50);
	gGameExternalOptions.ubMercRandomExpRange				= iniReader.ReadInteger("Recruitment Settings", "MERCS_RANDOM_EXP_RANGE", 1, 0, 4);
	gGameExternalOptions.fMercRandomSkillTraitPools			= iniReader.ReadBoolean("Recruitment Settings", "MERCS_RANDOM_SKILL_TRAIT_POOLS", FALSE); // rftr

JA2_Options.ini update with a blurb and the config (also placed with the above configs):
; MERCS_RANDOM_SKILL_TRAIT_POOLS applicable independent of MERCS_RANDOM_STATS
; This setting overrides any skill traits assigned by MERCS_RANDOM_STATS = 4.
; Randomly assigns skill traits based on pools defined in RandomStats.xml, overriding their default skill traits.
; The minimum number of skill traits is their JA2 1.13 default or the number of skill traits in their pool, whichever is lower.
; The maximum number of skill traits is their minimum, plus one. This is also capped by the number of skill traits in the pool.
;------------------------------------------------------------------------------------------------------------------------------

MERCS_RANDOM_STATS = 0
MERCS_RANDOM_GEAR_KITS = FALSE
MERCS_RANDOM_BELL_DISTRIBUTION = TRUE
MERCS_RANDOM_STAT_RANGE = 10
MERCS_RANDOM_EXP_RANGE = 1
MERCS_RANDOM_SKILL_TRAIT_POOLS = FALSE

I've gone ahead and added skill trait pools to most NPCs in the game. They may not be personality-appropriate but it'll serve as an example of how to customize RandomStats.xml:
https://pastebin.com/ie5aDVXF

[Updated on: Sat, 20 February 2021 08:52]

Report message to a moderator

Private 1st Class
Re: Code Snippets[message #362453 is a reply to message #362448] Sat, 20 February 2021 21:24 Go to previous messageGo to next message
rftr is currently offline rftr

 
Messages:32
Registered:August 2020
I've also got a minor adjustment in CheckForEndOfBattle() to not fire maluses if the enemy team is unaware of the player's presence (due to spy actions, leaving sector asap, etc). This probably needs more testing but it's been behaving as I expect so far.
BOOLEAN CheckForEndOfBattle( BOOLEAN fAnEnemyRetreated )
<snip>
    if ( fBattleLost )
    {
        // CJC: End AI's turn here.... first... so that UnSetUIBusy will succeed if militia win
        // battle for us
        EndAllAITurns( );

        // Set enemy presence to false
        // This is safe 'cause we're about to unload the friggen sector anyway....
        gTacticalStatus.fEnemyInSector = FALSE;
        // SANDRO - reset number of enemies here
        memset( &(gTacticalStatus.bNumFoughtInBattle), 0, MAXTEAMS );

        // If here, the battle has been lost!
        UnSetUIBusy( (UINT8)gusSelectedSoldier );

        if ( gTacticalStatus.uiFlags & INCOMBAT )
        {
            // Exit mode!
            ExitCombatMode();
        }

        // rftr: only do Bad Things (tm) if the enemy was aware of us before we "lost"!
        if (gTacticalStatus.Team[ENEMY_TEAM].bAwareOfOpposition)
        {
            HandleMoraleEvent(NULL, MORALE_HEARD_BATTLE_LOST, gWorldSectorX, gWorldSectorY, gbWorldSectorZ);
            HandleGlobalLoyaltyEvent(GLOBAL_LOYALTY_BATTLE_LOST, gWorldSectorX, gWorldSectorY, gbWorldSectorZ);

            // SANDRO - end quest if cleared the sector after interrogation (sector N7 by Meduna)
            if ( gWorldSectorX == gModSettings.ubMeanwhileInterrogatePOWSectorX && gWorldSectorY == gModSettings.ubMeanwhileInterrogatePOWSectorY &&
                gbWorldSectorZ == 0 && gubQuest[ QUEST_INTERROGATION ] == QUESTINPROGRESS )
            {
                // Quest failed
                InternalEndQuest( QUEST_INTERROGATION, gWorldSectorX, gWorldSectorY, FALSE );
            }

            // Play death music
            #ifdef NEWMUSIC
            GlobalSoundID  = MusicSoundValues[ SECTOR( gWorldSectorX, gWorldSectorY ) ].SoundTacticalDeath[gbWorldSectorZ];
            if ( MusicSoundValues[ SECTOR( gWorldSectorX, gWorldSectorY ) ].SoundTacticalDeath[gbWorldSectorZ] != -1 )
                SetMusicModeID( MUSIC_TACTICAL_DEATH, MusicSoundValues[ SECTOR( gWorldSectorX, gWorldSectorY ) ].SoundTacticalDeath[gbWorldSectorZ] );
            else
            #endif
            SetMusicMode( MUSIC_TACTICAL_DEATH );

            if ( CheckFact( FACT_FIRST_BATTLE_BEING_FOUGHT, 0 ) )
            {
                // this is our first battle... and we lost it!
                SetFactTrue( FACT_FIRST_BATTLE_FOUGHT );
                SetFactFalse( FACT_FIRST_BATTLE_BEING_FOUGHT );
                SetTheFirstBattleSector( (INT16) (gWorldSectorX + gWorldSectorY * MAP_WORLD_X) );
#ifdef JA2UB
                //Ja25  no meanwhile
#else
                HandleFirstBattleEndingWhileInTown( gWorldSectorX, gWorldSectorY, gbWorldSectorZ, FALSE );
#endif
            }

            LogBattleResults( LOG_DEFEAT );
        }

        SetCustomizableTimerCallbackAndDelay( 10000, DeathNoMessageTimerCallback, FALSE );

        if (is_networked)
            ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, MPClientMessage[42] );

        if( NumEnemyInSectorExceptCreatures() )
        {
            SetThisSectorAsEnemyControlled( gWorldSectorX, gWorldSectorY, gbWorldSectorZ, TRUE );
        }

        // ATE: Important! THis is delayed until music ends so we can have proper effect!
        // CheckAndHandleUnloadingOfCurrentWorld();

        //Whenever returning TRUE, make sure you clear gfBlitBattleSectorLocator;
        gfBlitBattleSectorLocator = FALSE;

        // Flugente: in any case, reset creature attack variables
        ResetCreatureAttackVariables();

        // If we are the server, we escape this function at the top if we think the game should still be running
        // hence if we get here the game is over for all clients and we should report it
        if (is_networked && is_server)
            game_over();
        return( TRUE );
    }

Report message to a moderator

Private 1st Class
Re: Code Snippets[message #362454 is a reply to message #362453] Sat, 20 February 2021 21:37 Go to previous messageGo to next message
Deleted.

 
Messages:2657
Registered:December 2012
Location: Russian Federation
As far as I understand the code, bAwareOfOpposition will not be set if sector is jammed.
In +AI, I use more advanced version with option which allows to control defeat behavior.
 // We should NEVER have a battle lost and won at the same time...
if (fBattleLost)
{
	// sevenfm: count alive/dead/not covert mercs in sector/retreating from sector
	UINT8 ubLoop = gTacticalStatus.Team[gbPlayerNum].bFirstID;
	BOOLEAN fFoundNotCovertMerc = FALSE;
	BOOLEAN fFoundAliveMerc = FALSE;
	BOOLEAN fFoundDeadMerc = FALSE;
	for (pTeamSoldier = MercPtrs[ubLoop]; ubLoop <= gTacticalStatus.Team[gbPlayerNum].bLastID; ubLoop++, pTeamSoldier++)
	{
		if (pTeamSoldier->bActive)
		{
			if (pTeamSoldier->bInSector ||
				//pTeamSoldier->flags.fBetweenSectors && SECTORX(pTeamSoldier->ubPrevSectorID) == gWorldSectorX && SECTORY(pTeamSoldier->ubPrevSectorID) == gWorldSectorY && (pTeamSoldier->bSectorZ == gbWorldSectorZ) ||
				pTeamSoldier->flags.fBetweenSectors && pTeamSoldier->sSectorX == gWorldSectorX && pTeamSoldier->sSectorY == gWorldSectorY && pTeamSoldier->bSectorZ == gbWorldSectorZ)
			{
				if (pTeamSoldier->stats.bLife >= OKLIFE)
				{
					fFoundAliveMerc = TRUE;
					if (!gGameOptions.fNewTraitSystem || !pTeamSoldier->IsCovert())
					{
						fFoundNotCovertMerc = TRUE;
					}
				}
				else
				{
					fFoundDeadMerc = TRUE;
				}
			}
		}
	}
	BOOLEAN fDefeat = FALSE;

	// sevenfm: determine if we should consider this as defeat
	if (gGameExternalOptions.ubDefeatMode == 0 ||
		gGameExternalOptions.ubDefeatMode == 1 && TeamEnemyAlerted(gbPlayerNum) ||
		gGameExternalOptions.ubDefeatMode == 2 && fFoundNotCovertMerc ||
		gGameExternalOptions.ubDefeatMode == 3 && fFoundDeadMerc ||
		gGameExternalOptions.ubDefeatMode == 4 && !fFoundAliveMerc)
		fDefeat = TRUE;
...
// sevenfm: only apply morale and loyalty penalty if player was defeated
	if (fDefeat)
	{
		HandleMoraleEvent(NULL, MORALE_HEARD_BATTLE_LOST, gWorldSectorX, gWorldSectorY, gbWorldSectorZ);
		HandleGlobalLoyaltyEvent(GLOBAL_LOYALTY_BATTLE_LOST, gWorldSectorX, gWorldSectorY, gbWorldSectorZ);
	}
; Determine when the lost battle is considered defeat 
; (which results in MORALE_HEARD_BATTLE_LOST and GLOBAL_LOYALTY_BATTLE_LOST penalties, also playing MUSIC_TACTICAL_DEATH and writing defeat record in the game log)
; 0: default, any lost battle is considered defeat
; 1: if found alerted opponent in sector
; 2: if at least one of retreating mercs is not covert
; 3: if at least one merc was killed in battle
; 4: if all mercs were killed in battle
DEFEAT_MODE = 0



Left this community.

Report message to a moderator

Lieutenant

Re: Code Snippets[message #362455 is a reply to message #362454] Sat, 20 February 2021 21:41 Go to previous messageGo to next message
rftr is currently offline rftr

 
Messages:32
Registered:August 2020
Ha, I figured there was a gotcha. Any plans to bring your change into the trunk?

Report message to a moderator

Private 1st Class
Re: Code Snippets[message #362456 is a reply to message #362455] Sat, 20 February 2021 21:47 Go to previous messageGo to next message
Deleted.

 
Messages:2657
Registered:December 2012
Location: Russian Federation
rftr wrote on Sun, 21 February 2021 00:41
Any plans to bring your change into the trunk?
Probably yes, why not.



Left this community.

Report message to a moderator

Lieutenant

Re: Code Snippets[message #362768 is a reply to message #172712] Sat, 27 March 2021 09:21 Go to previous message
rftr is currently offline rftr

 
Messages:32
Registered:August 2020
I've made a change to add robots to the ASD. They're usually more common than jeeps and tanks, and are effectively the army's heavy infantry. I haven't done a playthrough to see if they're balanced or not, but they will make the endgame a little tougher.

https://drive.google.com/drive/folders/1RyoMJjRCuH2-Yf_D_M_Cj2yoIKw1DEpp?usp=sharing

Report message to a moderator

Private 1st Class
Previous Topic: New Attachment System Beta
Goto Forum:
  


Current Time: Wed Nov 27 04:31:28 GMT+2 2024

Total time taken to generate the page: 0.03060 seconds