UCRP_UFOATTACKMETHOD

`Condition1 = UfoAttackMethod [type]`
    - "smash a UFO Catcher using only [type]"
        - Combine with `Prefix_SealedStar` or `Prefix_IsMap [special stage stage]`
        - Shows up as "???"
    - Types supported:
        - `Boost` - "boost power" (sneakers)
        - `Whip` - "Insta-Whip"
        - `Banana` - "Bananas"
        - `Orbinaut`- "Orbinauts"
        - `Jawz` - "Jawz"
        - `SPB` - "Self Propelled Bombs"
    - Other types could be added on request, these were just the easy ones

In addition, the prototype for P_MobjWasRemoved was moved to `p_mobj.h`.
It's EXTREMELY important that we're able to safely check mobj pointers anywhere a mobj_t is possible to observe, without including the full `p_local.h`...
This commit is contained in:
toaster 2023-10-17 01:07:03 +01:00
parent f0d1813752
commit d11fe78e90
7 changed files with 139 additions and 10 deletions

View file

@ -387,6 +387,18 @@ struct botvars_t
// player_t struct for round-specific condition tracking
typedef enum
{
UFOD_GENERIC = 1,
UFOD_BOOST = 1<<1,
UFOD_WHIP = 1<<2,
UFOD_BANANA = 1<<3,
UFOD_ORBINAUT = 1<<4,
UFOD_JAWZ = 1<<5,
UFOD_SPB = 1<<6,
// free up to and including 1<<31
} ufodamaging_t;
struct roundconditions_t
{
// Reduce the number of checks by only updating when this is true
@ -404,6 +416,8 @@ struct roundconditions_t
UINT8 hittrackhazard[((MAX_LAPS+1)/8) + 1];
ufodamaging_t ufodamaging;
mobjeflag_t wet_player;
// 32 triggers, one bit each, for map execution

View file

@ -3007,6 +3007,25 @@ static void readcondition(UINT16 set, UINT32 id, char *word2)
}
}
}
else if (fastcmp(params[0], "UFOATTACKMETHOD"))
{
PARAMCHECK(1);
ty = UCRP_UFOATTACKMETHOD;
// See ufodamaging_t
if ((offset=1) || fastcmp(params[1], "BOOST")
|| (++offset && fastcmp(params[1], "WHIP"))
|| (++offset && fastcmp(params[1], "BANANA"))
|| (++offset && fastcmp(params[1], "ORBINAUT"))
|| (++offset && fastcmp(params[1], "JAWZ"))
|| (++offset && fastcmp(params[1], "SPB")))
re = offset;
else
{
deh_warning("Unknown attack method %s for condition ID %d", params[1], id+1);
return;
}
}
else
{
deh_warning("Invalid condition name %s for condition ID %d", params[0], id+1);

View file

@ -1683,6 +1683,16 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
return (!(player->roundconditions.hittrackhazard[requiredlap] & requiredbit) != (cn->requirement == 1));
}
case UCRP_UFOATTACKMETHOD:
return (
specialstageinfo.valid == true
&& (
P_MobjWasRemoved(specialstageinfo.ufo)
|| specialstageinfo.ufo->health <= 1
)
&& player->roundconditions.ufodamaging == (ufodamaging_t)(1<<cn->requirement)
);
case UCRP_WETPLAYER:
return (((player->roundconditions.wet_player & cn->requirement) == 0)
&& !player->roundconditions.fell_off); // Levels with water tend to texture their pits as water too
@ -2377,6 +2387,43 @@ static const char *M_GetConditionString(condition_t *cn)
return va("%s on lap %u", work, cn->extrainfo1);
}
case UCRP_UFOATTACKMETHOD:
{
if (!gamedata->everseenspecial)
return NULL;
work = NULL;
switch (cn->requirement)
{
case 1:
work = "boost power";
break;
case 2:
work = "Insta-Whip";
break;
case 3:
work = "Bananas";
break;
case 4:
work = "Orbinauts";
break;
case 5:
work = "Jawz";
break;
case 6:
work = "Self Propelled Bombs";
break;
default:
break;
}
if (work == NULL)
return va("INVALID ATTACK CONDITION \"%d:%d\"", cn->type, cn->requirement);
return va("smash the UFO Catcher using only %s", work);
}
case UCRP_WETPLAYER:
return va("without %s %s",
(cn->requirement & MFE_TOUCHWATER) ? "touching any" : "going into",

View file

@ -124,6 +124,8 @@ typedef enum
UCRP_TRACKHAZARD, // (Don't) get hit by a track hazard (maybe specific lap)
UCRP_UFOATTACKMETHOD, // Defeat a UFO Catcher using only one method
UCRP_WETPLAYER, // Don't touch [strictness] [fluid]
} conditiontype_t;

View file

@ -763,52 +763,80 @@ static void UFOKillPieces(mobj_t *ufo)
static UINT8 GetUFODamage(mobj_t *inflictor, UINT8 damageType)
{
UINT8 ret = 0;
ufodamaging_t ufodamaging = UFOD_GENERIC;
if (inflictor != NULL && P_MobjWasRemoved(inflictor) == false)
{
switch (inflictor->type)
{
// Shields deal chip damage.
case MT_JAWZ_SHIELD:
{
ufodamaging = UFOD_JAWZ;
ret = 10;
break;
}
case MT_ORBINAUT_SHIELD:
{
ufodamaging = UFOD_ORBINAUT;
ret = 10;
break;
}
case MT_INSTAWHIP:
{
// Shields deal chip damage.
return 10;
ufodamaging = UFOD_WHIP;
ret = 10;
break;
}
case MT_JAWZ:
{
// Thrown Jawz deal a bit extra.
return 15;
ufodamaging = UFOD_JAWZ;
ret = 15;
break;
}
case MT_ORBINAUT:
{
// Thrown orbinauts deal double damage.
return 20;
ufodamaging = UFOD_ORBINAUT;
ret = 20;
break;
}
case MT_SPB:
{
// SPB deals triple damage.
return 30;
ufodamaging |= UFOD_SPB;
ret = 30;
break;
}
case MT_BANANA:
{
ufodamaging = UFOD_BANANA;
// Banana snipes deal triple damage,
// laid down bananas deal regular damage.
if (inflictor->health > 1)
{
return 30;
ret = 30;
break;
}
return 10;
ret = 10;
break;
}
case MT_PLAYER:
{
// Players deal damage relative to how many sneakers they used.
return 15 * max(1, inflictor->player->numsneakers);
ufodamaging = UFOD_BOOST;
ret = 15 * max(1, inflictor->player->numsneakers);
break;
}
case MT_SPECIAL_UFO:
{
// UFODebugSetHealth
return 1;
ret = 1;
break;
}
default:
{
@ -817,6 +845,23 @@ static UINT8 GetUFODamage(mobj_t *inflictor, UINT8 damageType)
}
}
{
// We have to iterate over all players, otherwise a player who gets exactly one hit in will trick the Challenges system.
UINT8 i;
for (i = 0; i <= splitscreen; i++)
{
if (!playeringame[g_localplayers[i]])
continue;
if (players[g_localplayers[i]].spectator)
continue;
players[i].roundconditions.ufodamaging |= ufodamaging;
players[i].roundconditions.checkthisframe = true;
}
}
if (ret)
return ret;
// Guess from damage type.
switch (damageType & DMG_TYPEMASK)
{

View file

@ -260,7 +260,6 @@ void P_RecalcPrecipInSector(sector_t *sector);
void P_PrecipitationEffects(void);
void P_RemoveMobj(mobj_t *th);
boolean P_MobjWasRemoved(const mobj_t *th);
void P_RemoveSavegameMobj(mobj_t *th);
boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state);
boolean P_SetMobjState(mobj_t *mobj, statenum_t state);

View file

@ -509,6 +509,9 @@ struct precipmobj_t
tic_t lastThink;
};
// It's extremely important that all mobj_t*-reading code have access to this.
boolean P_MobjWasRemoved(const mobj_t *th);
struct actioncache_t
{
actioncache_t *next;