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!
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.
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.
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.
//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?
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++;
}
}
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?):
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:",
};
@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.
// 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:
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).
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
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 );
}
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
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.