Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix interaction when pushing character off of falling tile #563

Merged
merged 3 commits into from Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions DRODLib/Character.cpp
Expand Up @@ -7228,6 +7228,10 @@ void CCharacter::MoveCharacter(
room.ActivateToken(CueEvents, this->wX, this->wY, this);
}

//If another monster was pushed, the destination tile may have fallen
if (this->bPushedOtherMonster)
room.CheckForFallingAt(this->wX, this->wY, CueEvents);

SetWeaponSheathed();
RefreshBriars();

Expand Down
3 changes: 3 additions & 0 deletions DRODLib/Construct.cpp
Expand Up @@ -232,6 +232,9 @@ void CConstruct::Process(
UINT tTile = room.GetTSquare(this->wX, this->wY);
if (bIsTLayerCoveringItem(tTile))
room.PushTLayerObject(this->wX, this->wY, this->wX + dx, this->wY + dy, CueEvents);
//If another monster was pushed, the destination tile may have fallen
if (this->bPushedOtherMonster)
room.CheckForFallingAt(this->wX, this->wY, CueEvents);
}
}

Expand Down
8 changes: 5 additions & 3 deletions DRODLib/CurrentGame.cpp
Expand Up @@ -5996,6 +5996,7 @@ void CCurrentGame::ProcessPlayer(
const UINT wTTileNo = this->pRoom->GetTSquare(this->swordsman.wX, this->swordsman.wY);
bool bEnteredTunnel = false;
bool bMovingPlatform = false;
bool bPushedCharacter = false;

//Look for obstacles and set dx/dy accordingly.
const UINT wMoveO = nGetO(dx, dy);
Expand Down Expand Up @@ -6252,9 +6253,6 @@ void CCurrentGame::ProcessPlayer(
//Player bumps into an NPC, see if we can push him first
CCharacter *pCharacter = DYN_CAST(CCharacter*, CMonster*, pMonster);


bool bPushedCharacter = false;

if (pCharacter->IsPushableByBody()){
const UINT wDestX = pCharacter->wX + dx;
const UINT wDestY = pCharacter->wY + dy;
Expand Down Expand Up @@ -6360,6 +6358,10 @@ void CCurrentGame::ProcessPlayer(
this->pRoom->DestroyTrapdoor(this->swordsman.wX - dx,
this->swordsman.wY - dy, CueEvents);

//If a character was pushed, the destination tile may have fallen
if (bPushedCharacter)
this->pRoom->CheckForFallingAt(this->swordsman.wX, this->swordsman.wY, CueEvents);

//Check for stepping on monster
CMonster* pMonster = this->pRoom->GetMonsterAtSquare(this->swordsman.wX, this->swordsman.wY);
if (pMonster)
Expand Down
4 changes: 4 additions & 0 deletions DRODLib/Mimic.cpp
Expand Up @@ -347,6 +347,10 @@ void CMimic::ApplyMimicMove(int dx, int dy, int nCommand, const UINT wMovementO,
if (bIsPit(wOTile) || bIsDeepWater(wOTile))
room.MovePlatform(this->wX - dx, this->wY - dy, nGetO(dx,dy));
}

//If another monster was pushed, the destination tile may have fallen
if (this->bPushedOtherMonster)
room.CheckForFallingAt(this->wX, this->wY, CueEvents);
}

//Check for movement onto a checkpoint.
Expand Down
5 changes: 4 additions & 1 deletion DRODLib/Monster.cpp
Expand Up @@ -105,7 +105,7 @@ CMonster::CMonster(
, bAlive(true)
, bForceWeaponAttack(false)
, stunned(0), bNewStun(false)
, bPushedThisTurn(false)
, bPushedThisTurn(false), bPushedOtherMonster(false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest we reset this var in CMonster::Clear().

, bWaitedOnHotFloorLastTurn(false)
, pNext(NULL), pPrevious(NULL)
, pCurrentGame(NULL)
Expand Down Expand Up @@ -134,6 +134,7 @@ void CMonster::Clear()
this->stunned = 0;
this->bNewStun = false;
this->bPushedThisTurn = false;
this->bPushedOtherMonster = false;
this->ExtraVars.Clear();
while (this->Pieces.size())
{
Expand Down Expand Up @@ -2160,12 +2161,14 @@ void CMonster::Move(
CDbRoom& room = *(this->pCurrentGame->pRoom);
CMonster *pMonster = room.GetMonsterAtSquare(wDestX,wDestY);
bool bFluffPoison = false;
bPushedOtherMonster = false;
if (pMonster)
{
ASSERT(pCueEvents);

if (pMonster->IsPushableByBody() && this->CanPushMonsters()){
pMonster->PushInDirection(sgn(wDestX - this->wX), sgn(wDestY - this->wY), false, *pCueEvents);
bPushedOtherMonster = true;
}
else
{
Expand Down
1 change: 1 addition & 0 deletions DRODLib/Monster.h
Expand Up @@ -365,6 +365,7 @@ class CMonster : public CEntity
UINT stunned; //whether monster is stunned and skips turn
bool bNewStun; //whether monster stun was inflicted this turn
bool bPushedThisTurn; //whether monster was pushed this turn
bool bPushedOtherMonster; //if another monster was pushed during most recent movement
bool bWaitedOnHotFloorLastTurn; //for hasteable playerdoubles
bool bSafeToDelete;

Expand Down
172 changes: 172 additions & 0 deletions DRODLibTests/src/tests/Scripting/ImperativePushable/PushableByBody.cpp
Expand Up @@ -8,6 +8,19 @@ static void AddPushableCharacter(UINT x, UINT y){
RoomBuilder::AddCommand(character, CCharacterCommand::CC_Imperative, ScriptFlag::PushableByBody);
}

static void AddPushableDropperCharacter(UINT x, UINT y) {
CCharacter* character = RoomBuilder::AddVisibleCharacter(x, y);
RoomBuilder::AddCommand(character, CCharacterCommand::CC_Imperative, ScriptFlag::PushableByBody);
RoomBuilder::AddCommand(character, CCharacterCommand::CC_Behavior, ScriptFlag::DropTrapdoors, 1);
}

static void AddPusherCharacter(UINT x, UINT y, MovementType movement) {
CCharacter* character = RoomBuilder::AddVisibleCharacter(x, y);
RoomBuilder::AddCommand(character, CCharacterCommand::CC_SetMovementType, movement);
RoomBuilder::AddCommand(character, CCharacterCommand::CC_Behavior, ScriptFlag::PushMonsters, 1);
RoomBuilder::AddCommand(character, CCharacterCommand::CC_MoveRel, 1, 0);
}

static void TestCharacterCanBePushedByCharacterWithIdentity(const UINT identity) {
char name[100];
sprintf(name, "Character ID #%d should be able to push the character", identity);
Expand All @@ -24,6 +37,89 @@ static void TestCharacterCanBePushedByCharacterWithIdentity(const UINT identity)
}
}

static void TestDeadlyFallingTilePlayerPush(UINT tile) {
char name[100];
sprintf(name, "Player dies pushing trapdoor dropper from tile ID #%d", tile);

SECTION(name) {
RoomBuilder::Plot(tile, 10, 10);
AddPushableDropperCharacter(10, 10);

CCurrentGame* game = Runner::StartGame(9, 10, S);
Runner::ExecuteCommand(CMD_E);

AssertMonster(11, 10);
AssertPlayerIsDead();
}
}

static void TestDeadlyFallingTileMimicPush(UINT tile) {
char name[100];
sprintf(name, "Mimic dies pushing trapdoor dropper from tile ID #%d", tile);

SECTION(name) {
RoomBuilder::Plot(tile, 10, 10);
AddPushableDropperCharacter(10, 10);
RoomBuilder::AddMonster(M_MIMIC, 9, 10, S);

CCurrentGame* game = Runner::StartGame(15, 10, S);
Runner::ExecuteCommand(CMD_E);

AssertMonster(11, 10);
AssertNoMonster(10, 10);
}
}

static void TestDeadlyFallingTileConstructPush(UINT tile) {
char name[100];
sprintf(name, "Construct dies pushing trapdoor dropper from tile ID #%d", tile);

SECTION(name) {
RoomBuilder::Plot(tile, 10, 10);
AddPushableDropperCharacter(10, 10);
RoomBuilder::AddMonster(M_CONSTRUCT, 9, 10, S);

CCurrentGame* game = Runner::StartGame(15, 10, S);
Runner::ExecuteCommand(CMD_WAIT);

AssertMonster(11, 10);
AssertNoMonster(10, 10);
}
}

static void TestDeadlyFallingTileCharacterPush(MovementType movement, UINT tile) {
char name[100];
sprintf(name, "Character with movement %d dies pushing trapdoor dropper from tile ID #%d", movement, tile);

SECTION(name) {
RoomBuilder::Plot(tile, 10, 10);
AddPushableDropperCharacter(10, 10);
AddPusherCharacter(9, 10, movement);

CCurrentGame* game = Runner::StartGame(15, 10, S);
Runner::ExecuteCommand(CMD_E);

AssertMonster(11, 10);
AssertNoMonster(10, 10);
}
}

static void TestSafeFallingTileCharacterPush(MovementType movement, UINT tile) {
char name[100];
sprintf(name, "Character with movement %d survives pushing trapdoor dropper from tile ID #%d", movement, tile);

SECTION(name) {
RoomBuilder::Plot(tile, 10, 10);
AddPushableDropperCharacter(10, 10);
AddPusherCharacter(9, 10, movement);

CCurrentGame* game = Runner::StartGame(15, 10, S);
Runner::ExecuteCommand(CMD_E);

AssertMonster(11, 10);
AssertMonster(10, 10);
}
}

TEST_CASE("Invulnerable character with 'Imperative: Pushable by body'", "[game][player moves][beethro][scripting][imperative][push]") {
RoomBuilder::ClearRoom();
Expand Down Expand Up @@ -147,3 +243,79 @@ TEST_CASE("Invulnerable character with 'Imperative: Pushable by body'", "[game][
TestCharacterCanBePushedByCharacterWithIdentity(M_TARTECHNICIAN);
TestCharacterCanBePushedByCharacterWithIdentity(M_CONSTRUCT);
}

TEST_CASE("Push character off of falling tile", "[game][player moves][beethro][scripting][imperative][push]") {
RoomBuilder::ClearRoom();

TestDeadlyFallingTilePlayerPush(T_TRAPDOOR);
TestDeadlyFallingTilePlayerPush(T_TRAPDOOR2);
TestDeadlyFallingTilePlayerPush(T_THINICE);

SECTION("Player survives pushing trapdoor dropper from shallow thin ice") {
RoomBuilder::Plot(T_THINICE_SH, 10, 10);
AddPushableDropperCharacter(10, 10);

CCurrentGame* game = Runner::StartGame(9, 10, S);
Runner::ExecuteCommand(CMD_E);

AssertMonster(11, 10);
AssertPlayerIsAlive();
}

TestDeadlyFallingTileMimicPush(T_TRAPDOOR);
TestDeadlyFallingTileMimicPush(T_TRAPDOOR2);
TestDeadlyFallingTileMimicPush(T_THINICE);

SECTION("Mimic survives pushing trapdoor dropper from shallow thin ice") {
RoomBuilder::Plot(T_THINICE_SH, 10, 10);
AddPushableDropperCharacter(10, 10);
RoomBuilder::AddMonster(M_MIMIC, 9, 10, S);

CCurrentGame* game = Runner::StartGame(15, 10, S);
Runner::ExecuteCommand(CMD_E);

AssertMonsterType(10, 10, M_MIMIC);
AssertMonster(11, 10);
}

TestDeadlyFallingTileConstructPush(T_TRAPDOOR);
TestDeadlyFallingTileConstructPush(T_TRAPDOOR2);
TestDeadlyFallingTileConstructPush(T_THINICE);

SECTION("Construct survives pushing trapdoor dropper from shallow thin ice") {
RoomBuilder::Plot(T_THINICE_SH, 10, 10);
AddPushableDropperCharacter(10, 10);
RoomBuilder::AddMonster(M_CONSTRUCT, 9, 10, S);

CCurrentGame* game = Runner::StartGame(15, 10, S);
Runner::ExecuteCommand(CMD_WAIT);

AssertMonsterType(10, 10, M_CONSTRUCT);
AssertMonster(11, 10);
}

TestDeadlyFallingTileCharacterPush(GROUND, T_TRAPDOOR);
TestDeadlyFallingTileCharacterPush(GROUND, T_TRAPDOOR2);
TestDeadlyFallingTileCharacterPush(GROUND, T_THINICE);
TestDeadlyFallingTileCharacterPush(GROUND, T_THINICE_SH);

TestSafeFallingTileCharacterPush(AIR, T_TRAPDOOR);
TestSafeFallingTileCharacterPush(AIR, T_TRAPDOOR2);
TestSafeFallingTileCharacterPush(AIR, T_THINICE);
TestSafeFallingTileCharacterPush(AIR, T_THINICE_SH);

TestDeadlyFallingTileCharacterPush(WALL, T_TRAPDOOR);
TestDeadlyFallingTileCharacterPush(WALL, T_TRAPDOOR2);
TestDeadlyFallingTileCharacterPush(WALL, T_THINICE);
TestDeadlyFallingTileCharacterPush(WALL, T_THINICE_SH);

TestDeadlyFallingTileCharacterPush(WATER, T_TRAPDOOR);
TestSafeFallingTileCharacterPush(WATER, T_TRAPDOOR2);
TestSafeFallingTileCharacterPush(WATER, T_THINICE);
TestSafeFallingTileCharacterPush(WATER, T_THINICE_SH);

TestDeadlyFallingTileCharacterPush(GROUND_AND_SHALLOW_WATER, T_TRAPDOOR);
TestDeadlyFallingTileCharacterPush(GROUND_AND_SHALLOW_WATER, T_TRAPDOOR2);
TestDeadlyFallingTileCharacterPush(GROUND_AND_SHALLOW_WATER, T_THINICE);
TestSafeFallingTileCharacterPush(GROUND_AND_SHALLOW_WATER, T_THINICE_SH);
}