mirror of
https://github.com/1dot13/source.git
synced 2026-08-12 14:10:23 +02:00
HAM5.5b2 merged in!
Notes: -There may still be bugs!! (In fact, I almost guarantee it -- there's -Ammo box code added to HR's crate code -Splitting a crate/box also automatically refills half empty magazines -new pathing arrows for map screen not included -code for scouts to know where enemies are going doesn't appear to work... but i'm not sure it was completed. -lots of gobledy gook (chinese?) comments turned back to english -also added Bob's popups -other languages besides english still need to have there language.cpp files updated with array changes -AI enhancements by Warmsteel (reloading, suppression, roaming, avoiding gas, and other fixes) git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@5181 3b4a5df2-a311-0410-b5c6-a8a6f20db521
This commit is contained in:
@@ -2095,6 +2095,8 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
|
||||
// WANNE.TANK: Choose cannon or rocket
|
||||
UINT16 usHandItem = pSoldier->inv[HANDPOS].usItem;
|
||||
|
||||
INT8 bSlot;
|
||||
|
||||
if (TANK(pSoldier))
|
||||
{
|
||||
// No cannon selected to fire
|
||||
@@ -2790,6 +2792,16 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier)
|
||||
break;
|
||||
/////////////////////////////////////////////////////////////
|
||||
|
||||
case AI_ACTION_RELOAD_GUN:
|
||||
bSlot = FindAmmoToReload( pSoldier, pSoldier->aiData.usActionData, NO_SLOT );
|
||||
if(bSlot != NO_SLOT)
|
||||
{
|
||||
ReloadGun( pSoldier, &(pSoldier->inv[pSoldier->aiData.usActionData]), &(pSoldier->inv[bSlot]) );
|
||||
ActionDone( pSoldier );
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
#ifdef BETAVERSION
|
||||
NumMessage("ExecuteAction - Illegal action type = ",pSoldier->aiData.bAction);
|
||||
|
||||
+107
-41
@@ -1726,31 +1726,20 @@ BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo )
|
||||
// smoke
|
||||
if (gpWorldLevelData[sGridNo].ubExtFlags[ pSoldier->pathing.bLevel ] & MAPELEMENT_EXT_SMOKE)
|
||||
{
|
||||
return( TRUE );
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// tear/mustard gas
|
||||
if((gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_TEARGAS|MAPELEMENT_EXT_MUSTARDGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 200909
|
||||
return(TRUE);
|
||||
if ( gpWorldLevelData[sGridNo].ubExtFlags[ pSoldier->pathing.bLevel ] & MAPELEMENT_EXT_BURNABLEGAS )
|
||||
{
|
||||
return( TRUE );
|
||||
}
|
||||
|
||||
return(FALSE);
|
||||
return InGas( pSoldier, sGridNo );
|
||||
}
|
||||
|
||||
BOOLEAN InGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo )
|
||||
{
|
||||
// smoke/tear/mustard gas
|
||||
if((gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_SMOKE|MAPELEMENT_EXT_TEARGAS|MAPELEMENT_EXT_MUSTARDGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 230909
|
||||
return(TRUE);
|
||||
if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & MAPELEMENT_EXT_BURNABLEGAS )
|
||||
{
|
||||
return( TRUE );
|
||||
}
|
||||
|
||||
return(FALSE);
|
||||
// smoke
|
||||
if (gpWorldLevelData[sGridNo].ubExtFlags[ pSoldier->pathing.bLevel ] & MAPELEMENT_EXT_SMOKE)
|
||||
return TRUE;
|
||||
|
||||
return InGas(pSoldier,sGridNo);
|
||||
}
|
||||
|
||||
|
||||
@@ -1761,25 +1750,28 @@ INT16 InWaterOrGas(SOLDIERTYPE *pSoldier, INT32 sGridNo)
|
||||
return(TRUE);
|
||||
}
|
||||
|
||||
// tear/mustard gas
|
||||
if((gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_TEARGAS|MAPELEMENT_EXT_MUSTARDGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 200909
|
||||
return(TRUE);
|
||||
if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & MAPELEMENT_EXT_BURNABLEGAS )
|
||||
{
|
||||
return( TRUE );
|
||||
}
|
||||
|
||||
return(FALSE);
|
||||
return (INT16)InGas( pSoldier, sGridNo );
|
||||
}
|
||||
|
||||
BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo )
|
||||
{
|
||||
// tear/mustard gas
|
||||
if((gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_TEARGAS|MAPELEMENT_EXT_MUSTARDGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 200909
|
||||
return(TRUE);
|
||||
if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & MAPELEMENT_EXT_BURNABLEGAS )
|
||||
{
|
||||
return( TRUE );
|
||||
{//WarmSteel - One square away from gas is still considered in gas, because it could expand any moment.
|
||||
//Note: this only works for gas that expands with one tile, but hey it's better than nothing!
|
||||
int iNeighbourGridNo;
|
||||
for(int iDir = 0; iDir < NUM_WORLD_DIRECTIONS; ++iDir)
|
||||
{
|
||||
iNeighbourGridNo = sGridNo + DirectionInc(iDir);
|
||||
if(!TileIsOutOfBounds(iNeighbourGridNo))
|
||||
{
|
||||
// tear/mustard gas
|
||||
if((gpWorldLevelData[iNeighbourGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_TEARGAS|MAPELEMENT_EXT_MUSTARDGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 200909
|
||||
{
|
||||
return(TRUE);
|
||||
}
|
||||
if ( gpWorldLevelData[iNeighbourGridNo].ubExtFlags[pSoldier->pathing.bLevel] & MAPELEMENT_EXT_BURNABLEGAS )
|
||||
{
|
||||
return( TRUE );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return(FALSE);
|
||||
@@ -2249,6 +2241,7 @@ INT32 CalcManThreatValue( SOLDIERTYPE *pEnemy, INT32 sMyGrid, UINT8 ubReduceForC
|
||||
|
||||
INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo)
|
||||
{
|
||||
BOOL OppPosKnown = FALSE;
|
||||
if ( CREATURE_OR_BLOODCAT( pSoldier ) )
|
||||
{
|
||||
if ( pSoldier->aiData.bAlertStatus == STATUS_BLACK )
|
||||
@@ -2269,6 +2262,19 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo)
|
||||
*pusFromGridNo = pSoldier->aiData.sPatrolGrid[0];
|
||||
}
|
||||
|
||||
//Do we know about any opponent?
|
||||
for(UINT16 oppID = 0; oppID < MAX_NUM_SOLDIERS; oppID++)
|
||||
{
|
||||
if ( pSoldier->aiData.bOppList[oppID] != NOT_HEARD_OR_SEEN && gbPublicOpplist[pSoldier->bTeam][oppID] != NOT_HEARD_OR_SEEN)
|
||||
{
|
||||
OppPosKnown = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Externalize if people want?
|
||||
BOOL fLessRestrictiveRoaming = TRUE;
|
||||
|
||||
switch (pSoldier->aiData.bOrders)
|
||||
{
|
||||
// JA2 GOLD: give non-NPCs a 5 tile roam range for cover in combat when being shot at
|
||||
@@ -2281,16 +2287,68 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo)
|
||||
return( 5 );
|
||||
}
|
||||
case ONGUARD: return( 5 );
|
||||
case CLOSEPATROL: return( 15 );
|
||||
case RNDPTPATROL:
|
||||
case POINTPATROL: return(10 ); // from nextPatrolGrid, not whereIWas
|
||||
case FARPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED)
|
||||
case CLOSEPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED)
|
||||
{
|
||||
return( 25 );
|
||||
return( 5 );
|
||||
}
|
||||
else
|
||||
{
|
||||
return( 50 );
|
||||
if(!OppPosKnown || !fLessRestrictiveRoaming)
|
||||
{
|
||||
return( 15 );
|
||||
}
|
||||
else
|
||||
{
|
||||
return( 30 );
|
||||
//return( MAX_ROAMING_RANGE );
|
||||
}
|
||||
}
|
||||
case POINTPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED)
|
||||
{
|
||||
return( 10 );
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!OppPosKnown || !fLessRestrictiveRoaming)
|
||||
{
|
||||
return( 20 );
|
||||
}
|
||||
else
|
||||
{
|
||||
return( 40 );
|
||||
//return( MAX_ROAMING_RANGE );
|
||||
}
|
||||
} // from nextPatrolGrid, not whereIWas
|
||||
case RNDPTPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED)
|
||||
{
|
||||
return( 10 );
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!OppPosKnown || !fLessRestrictiveRoaming)
|
||||
{
|
||||
return( 20 );
|
||||
}
|
||||
else
|
||||
{
|
||||
//return( 40 );
|
||||
return( MAX_ROAMING_RANGE );
|
||||
}
|
||||
}// from nextPatrolGrid, not whereIWas
|
||||
case FARPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED)
|
||||
{
|
||||
return( 15 );
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!OppPosKnown || !fLessRestrictiveRoaming)
|
||||
{
|
||||
return( 30 );
|
||||
}
|
||||
else
|
||||
{
|
||||
return( MAX_ROAMING_RANGE );
|
||||
}
|
||||
}
|
||||
case ONCALL: if (pSoldier->aiData.bAlertStatus < STATUS_RED)
|
||||
{
|
||||
@@ -2298,7 +2356,15 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo)
|
||||
}
|
||||
else
|
||||
{
|
||||
return( 30 );
|
||||
if(!OppPosKnown || !fLessRestrictiveRoaming)
|
||||
{
|
||||
return( 30 );
|
||||
}
|
||||
else
|
||||
{
|
||||
//return(50);
|
||||
return( MAX_ROAMING_RANGE );
|
||||
}
|
||||
}
|
||||
case SEEKENEMY: *pusFromGridNo = pSoldier->sGridNo; // from current position!
|
||||
return(MAX_ROAMING_RANGE);
|
||||
|
||||
@@ -349,7 +349,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns
|
||||
continue; // next opponent
|
||||
|
||||
// calculate chance to REALLY hit: shoot accurately AND get past cover
|
||||
ubChanceToReallyHit = (ubBestChanceToHit * ubChanceToGetThrough) / 100;
|
||||
ubChanceToReallyHit = (UINT8)ceil((ubBestChanceToHit * ubChanceToGetThrough) / 100.0f);
|
||||
|
||||
// if we can't REALLY hit at all
|
||||
if (ubChanceToReallyHit == 0)
|
||||
|
||||
+41
-15
@@ -2100,7 +2100,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
|
||||
DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("DecideActionRed: soldier orders = %d",pSoldier->aiData.bOrders));
|
||||
|
||||
// if we have absolutely no action points, we can't do a thing under RED!
|
||||
if (!pSoldier->bActionPoints)
|
||||
if ( pSoldier->bActionPoints <= 0 ) //Action points can be negative
|
||||
{
|
||||
pSoldier->aiData.usActionData = NOWHERE;
|
||||
return(AI_ACTION_NONE);
|
||||
@@ -2163,7 +2163,8 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
|
||||
}
|
||||
}
|
||||
|
||||
if(WearGasMaskIfAvailable(pSoldier))//dnl ch40 200909
|
||||
//Only put mask on in gas
|
||||
if(bInGas && WearGasMaskIfAvailable(pSoldier))//dnl ch40 200909
|
||||
bInGas = InGasOrSmoke(pSoldier, pSoldier->sGridNo);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
@@ -2464,22 +2465,46 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
|
||||
return(AI_ACTION_NONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//RELOADING
|
||||
|
||||
// WarmSteel - Because of suppression fire, we need enough ammo to even consider suppressing
|
||||
// This means we need to reload. Also reload if we're just plainly low on bullets.
|
||||
if(BestShot.bWeaponIn != NO_SLOT
|
||||
&& ((pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft < gGameExternalOptions.ubAISuppressionMinimumAmmo && GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) >= gGameExternalOptions.ubAISuppressionMinimumMagSize)
|
||||
|| pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft < (UINT8)(GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) / 4) ))
|
||||
{
|
||||
// HEADROCK HAM 5: Fixed an issue where no ammo was found, leading to a crash when overloading the
|
||||
// inventory vector (bAmmoSlot = -1...)
|
||||
INT8 bAmmoSlot = FindAmmoToReload( pSoldier, BestShot.bWeaponIn, NO_SLOT );
|
||||
if (bAmmoSlot > -1)
|
||||
{
|
||||
OBJECTTYPE * pAmmo = &(pSoldier->inv[bAmmoSlot]);
|
||||
if((*pAmmo)[0]->data.ubShotsLeft > pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft && GetAPsToReloadGunWithAmmo( pSoldier, &(pSoldier->inv[BestShot.bWeaponIn]), pAmmo ) <= (INT16)pSoldier->bActionPoints)
|
||||
{
|
||||
pSoldier->aiData.usActionData = BestShot.bWeaponIn;
|
||||
return AI_ACTION_RELOAD_GUN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//SUPPRESSION FIRE
|
||||
|
||||
CheckIfShotPossible(pSoldier,&BestShot,TRUE);
|
||||
CheckIfShotPossible(pSoldier,&BestShot,TRUE); //WarmSteel - No longer returns 0 when there IS actually a chance to hit.
|
||||
|
||||
//must have a small chance to hit and the opponent must be on the ground (can't suppress guys on the roof)
|
||||
// HEADROCK HAM BETA2.4: Adjusted this for a random chance to suppress regardless of chance. This augments
|
||||
// current revamp of suppression fire.
|
||||
BOOLEAN fEnableAIAutofire = FALSE;
|
||||
|
||||
// CHRISL: Changed from a simple flag to two externalized values for more modder control over AI suppression
|
||||
if ( BestShot.bWeaponIn != -1 && (GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) >= gGameExternalOptions.ubAISuppressionMinimumMagSize && pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo) && ( ( BestShot.ubPossible && BestShot.ubChanceToReallyHit < 50 ) || (BestShot.ubPossible && BestShot.ubChanceToReallyHit < (INT16)PreRandom(100)) && Menptr[BestShot.ubOpponent].pathing.bLevel == 0 && pSoldier->aiData.bOrders != SNIPER ))
|
||||
fEnableAIAutofire = TRUE;
|
||||
|
||||
if (fEnableAIAutofire)
|
||||
// WarmSteel - Don't *always* try to suppress when under 50 CTH
|
||||
if ( BestShot.bWeaponIn != -1
|
||||
&& BestShot.ubPossible
|
||||
&& GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) >= gGameExternalOptions.ubAISuppressionMinimumMagSize
|
||||
&& pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo
|
||||
&& BestShot.ubChanceToReallyHit < (INT16)(PreRandom(100) - 50)
|
||||
&& Menptr[BestShot.ubOpponent].pathing.bLevel == 0
|
||||
&& pSoldier->aiData.bOrders != SNIPER )
|
||||
{
|
||||
// then do it!
|
||||
|
||||
@@ -2522,9 +2547,9 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
|
||||
// Make sure we decided to fire at least one shot!
|
||||
ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire );
|
||||
|
||||
// minimum 10 bullets
|
||||
// minimum 5 bullets //WarmSteel: 5 bullets sounds reasonable, no?
|
||||
// Hmmm, automatic suppression? Howcome we don't get this?
|
||||
if (pSoldier->bDoAutofire >= 10 && pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs )
|
||||
if (pSoldier->bDoAutofire >= 5 && pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs )
|
||||
{
|
||||
pSoldier->aiData.bAimTime = 0;
|
||||
pSoldier->bDoBurst = 1;
|
||||
@@ -3203,8 +3228,8 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK)
|
||||
guiRedHelpTimeTotal += (uiEndTime - uiStartTime);
|
||||
guiRedHelpCounter++;
|
||||
#endif
|
||||
|
||||
if (!TileIsOutOfBounds(sClosestFriend))
|
||||
//WarmSteel - Dont try if we're already quite close to our friend
|
||||
if (!TileIsOutOfBounds(sClosestFriend) && PythSpacesAway(pSoldier->sGridNo, sClosestFriend) > pSoldier->GetMaxDistanceVisible(sClosestFriend, 0, CALC_FROM_ALL_DIRS ))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// GO DIRECTLY TOWARDS CLOSEST FRIEND UNDER FIRE OR WHO LAST RADIOED
|
||||
@@ -3900,8 +3925,9 @@ INT16 ubMinAPCost;
|
||||
}
|
||||
}
|
||||
|
||||
if(WearGasMaskIfAvailable(pSoldier))//dnl ch40 200909
|
||||
bInGas = InGasOrSmoke(pSoldier, pSoldier->sGridNo);
|
||||
//Only put mask on in gas
|
||||
if(bInGas && WearGasMaskIfAvailable(pSoldier))//dnl ch40 200909
|
||||
bInGas = InGasOrSmoke(pSoldier, pSoldier->sGridNo);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// IF GASSED, OR REALLY TIRED (ON THE VERGE OF COLLAPSING), TRY TO RUN AWAY
|
||||
|
||||
@@ -651,7 +651,8 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
|
||||
// HEADROCK HAM 3.6: This doesn't take into account the 100AP system. Adjusting.
|
||||
// Please note, I used a calculation that may have a better representation in some global variable.
|
||||
//iMaxMoveTilesLeft = __max( 0, pSoldier->bActionPoints - MinAPsToStartMovement( pSoldier, usMovementMode ) );
|
||||
iMaxMoveTilesLeft = __max( 0, (pSoldier->bActionPoints - MinAPsToStartMovement( pSoldier, usMovementMode ) / (APBPConstants[AP_MAXIMUM]/25)) );
|
||||
// WarmSteel - Bugfix: wrong parentheses
|
||||
iMaxMoveTilesLeft = __max( 0, (pSoldier->bActionPoints - MinAPsToStartMovement( pSoldier, usMovementMode )) / (APBPConstants[AP_MAXIMUM] / 25) );
|
||||
|
||||
//NumMessage("In BLACK, maximum tiles to move left = ",maxMoveTilesLeft);
|
||||
|
||||
@@ -799,7 +800,17 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB
|
||||
//PopMessage(tempstr);
|
||||
}
|
||||
|
||||
iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
|
||||
// reduce cover for each person adjacent to this gridno who is on our team,
|
||||
// by 10% (so locations next to several people will be very much frowned upon
|
||||
if ( iCurrentCoverValue >= 0 )
|
||||
{
|
||||
iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
|
||||
}
|
||||
else
|
||||
{
|
||||
// when negative, must add a negative to decrease the total
|
||||
iCurrentCoverValue += (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo );
|
||||
}
|
||||
|
||||
#ifdef DEBUGCOVER
|
||||
// AINumMessage("Search Range = ",iSearchRange);
|
||||
|
||||
@@ -98,6 +98,8 @@ typedef enum
|
||||
AI_ACTION_OFFER_SURRENDER, // offer surrender to the player
|
||||
AI_ACTION_RAISE_GUN,
|
||||
AI_ACTION_STEAL_MOVE, // added by SANDRO
|
||||
|
||||
AI_ACTION_RELOAD_GUN,
|
||||
} ActionType;
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user