Skip to content

Commit

Permalink
- Fix SEQ playing incorrect animation and/or AI game crash on load save.
Browse files Browse the repository at this point in the history
gModernMap:
- Fix incorrect kModernThingEnemyLifeLeech detection for custom dudes.
- Fix Nblood hanging on load save in some maps (use no TROR for cansee func).
- Dude patrol: Do not try to restart if the marker is not found.
- Dude patrol: Force disable stealth flag for multiplayer game
  • Loading branch information
NoOneBlood committed Oct 17, 2024
1 parent 78f3de6 commit 92eebf5
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 31 deletions.
2 changes: 1 addition & 1 deletion source/blood/src/ai.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1685,7 +1685,7 @@ void aiInitSprite(spritetype *pSprite)

// start new patrol
pXSprite->target = -1;
aiPatrolSetMarker(pSprite, pXSprite);
patrol = (aiPatrolSetMarker(pSprite, pXSprite) > 0);
}
else if (rngok(target, 1, kMaxSprites))
{
Expand Down
13 changes: 12 additions & 1 deletion source/blood/src/loadsave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,19 @@ void LoadSaveSetup(void)
void nnExtLoadSaveConstruct(void);
#endif
myLoadSave = new MyLoadSave();

// NoOne: Seq must be in top of AI because of AISTATE callbacks (and seq callbacks in general).
// Ex: cultist throwing TNT - if you saved the game while it plays animation
// before fire trigger seq flag, you will get assertion fail on load since
// target == -1 after aiInitSprite() call.

// Another reason is that it just spawns the wrong animation.
// Ex: save the game when some dude moves, load it and quckly
// use ONERING cheat. You will see that dude plays moving
// animation, but have idle AISTATE after aiInitSprite()
// call. In vanilla they just stand still.

SeqLoadSaveConstruct();
ActorLoadSaveConstruct();
AILoadSaveConstruct();
EndGameLoadSaveConstruct();
Expand All @@ -562,7 +574,6 @@ void LoadSaveSetup(void)
MessagesLoadSaveConstruct();
MirrorLoadSaveConstruct();
PlayerLoadSaveConstruct();
SeqLoadSaveConstruct();
TriggersLoadSaveConstruct();
ViewLoadSaveConstruct();
WarpLoadSaveConstruct();
Expand Down
15 changes: 9 additions & 6 deletions source/blood/src/nnextcdud.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1507,13 +1507,16 @@ void CUSTOMDUDE_SETUP::SetupLeech()
spritetype* pSpr2 = &sprite[i];
if (pSpr2->owner == pSpr->index && xspriRangeIsFine(pSpr2->extra))
{
XSPRITE* pXSpr2 = &xsprite[pSpr2->extra];
if (pXSpr2->locked)
pDude->LeechKill(false); // repeat fake killing to set 0 ammo
if (pSpr2->type == kModernThingEnemyLifeLeech)
{
XSPRITE *pXSpr2 = &xsprite[pSpr2->extra];
if (pXSpr2->locked)
pDude->LeechKill(false); // repeat fake killing to set 0 ammo

// found!
pDude->pXLeech = pXSpr2;
break;
// found!
pDude->pXLeech = pXSpr2;
break;
}
}
}
}
Expand Down
120 changes: 98 additions & 22 deletions source/blood/src/nnexts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6538,7 +6538,80 @@ bool markerIsNode(XSPRITE* pXMark, bool back) {

}

void aiPatrolSetMarker(spritetype* pSprite, XSPRITE* pXSprite) {
int32_t cansee_NOTROR(int32_t x1, int32_t y1, int32_t z1, int16_t orig_sect1, int32_t x2, int32_t y2, int32_t z2, int16_t orig_sect2, int32_t wallmask = CSTAT_WALL_1WAY)
{
int16_t sect1 = orig_sect1;
int16_t sect2 = orig_sect2;
int32_t dacnt, danum;
const int32_t x21 = x2-x1, y21 = y2-y1, z21 = z2-z1;

static uint8_t sectbitmap[bitmap_size(MAXSECTORS)];
Bmemset(sectbitmap, 0, sizeof(sectbitmap));
if (x1 == x2 && y1 == y2)
return (sect1 == sect2);


bitmap_set(sectbitmap, sect1);
clipsectorlist[0] = sect1; danum = 1;

for (dacnt=0; dacnt<danum; dacnt++)
{
const int32_t dasectnum = clipsectorlist[dacnt];
auto const sec = (usectorptr_t)&sector[dasectnum];
uwallptr_t wal;
bssize_t cnt;

for (cnt=sec->wallnum,wal=(uwallptr_t)&wall[sec->wallptr]; cnt>0; cnt--,wal++)
{
auto const wal2 = (uwallptr_t)&wall[wal->point2];
const int32_t x31 = wal->x-x1, x34 = wal->x-wal2->x;
const int32_t y31 = wal->y-y1, y34 = wal->y-wal2->y;

int32_t x, y, z, nexts, t, bot;
int32_t cfz[2];

bot = y21*x34-x21*y34; if (bot <= 0) continue;
// XXX: OVERFLOW
t = y21*x31-x21*y31; if ((unsigned)t >= (unsigned)bot) continue;
t = y31*x34-x31*y34;
if ((unsigned)t >= (unsigned)bot)
{
continue;
}

nexts = wal->nextsector;
if (nexts < 0 || wal->cstat & wallmask)
return 0;

t = divscale24(t,bot);
x = x1 + mulscale24(x21,t);
y = y1 + mulscale24(y21,t);
z = z1 + mulscale24(z21,t);

getzsofslope(dasectnum, x,y, &cfz[0],&cfz[1]);
if (z <= cfz[0] || z >= cfz[1])
return 0;

getzsofslope(nexts, x,y, &cfz[0],&cfz[1]);
if (z <= cfz[0] || z >= cfz[1])
return 0;

if (!bitmap_test(sectbitmap, nexts))
{
bitmap_set(sectbitmap, nexts);
clipsectorlist[danum++] = nexts;
}
}
}

if (bitmap_test(sectbitmap, sect2))
return 1;

return 0;
}


char aiPatrolSetMarker(spritetype* pSprite, XSPRITE* pXSprite) {

spritetype* pNext = NULL; XSPRITE* pXNext = NULL;
spritetype* pCur = NULL; XSPRITE* pXCur = NULL;
Expand Down Expand Up @@ -6568,7 +6641,7 @@ void aiPatrolSetMarker(spritetype* pSprite, XSPRITE* pXSprite) {
continue;

GetSpriteExtents(pNext, &zt1, &zb1); GetSpriteExtents(pSprite, &zt2, &zb2);
if (cansee(pNext->x, pNext->y, zt1, pNext->sectnum, pSprite->x, pSprite->y, zt2, pSprite->sectnum))
if (cansee_NOTROR(pSprite->x, pSprite->y, zt2, pSprite->sectnum, pNext->x, pNext->y, zt1, pNext->sectnum))
{
closest = dist;
path = pNext->index;
Expand Down Expand Up @@ -6614,20 +6687,21 @@ void aiPatrolSetMarker(spritetype* pSprite, XSPRITE* pXSprite) {

if (firstFinePath == -1)
{
viewSetSystemMessage("No markers with id #%d found for dude #%d! (back = %d)", next, pSprite->index, back);
return;
consoleSysMsg("No markers with id #%d found for dude #%d! (back = %d)", next, pSprite->index, back);
return 0;
}

if (path == -1)
path = firstFinePath;
}

if (!spriRangeIsFine(path))
return;
return 0;

pXSprite->target = path;
pXSprite->targetX = prev; // keep previous marker index here, use actual sprite coords when selecting direction
sprite[path].owner = pSprite->index;
return 1;

}

Expand Down Expand Up @@ -6944,8 +7018,9 @@ int aiPatrolSearchTargets(spritetype* pSprite, XSPRITE* pXSprite) {

nnExResetPatrolBonkles();
int i, j, f, mod, x, y, z, dx, dy, nDist, eyeAboveZ, target = -1, sndCnt = 0, seeDist, hearDist, feelDist, seeChance, hearChance;
bool stealth = (pXSprite->unused1 & kDudeFlagStealth); bool blind = (pXSprite->dudeGuard); bool deaf = (pXSprite->dudeDeaf);
int nRandomSkill = Random(gGameOptions.nDifficulty);
bool stealth = ((pXSprite->unused1 & kDudeFlagStealth) && gGameOptions.nGameType == kGameTypeSinglePlayer);
bool blind = (pXSprite->dudeGuard); bool deaf = (pXSprite->dudeDeaf);
int nRandomSkill = QRandom(gGameOptions.nDifficulty);

// search for player targets
for (i = connecthead; i != -1; i = connectpoint2[i]) {
Expand Down Expand Up @@ -7290,10 +7365,16 @@ void aiPatrolFlagsMgr(spritetype* pSource, XSPRITE* pXSource, spritetype* pDest,
pXDest->target = -1; // reset the target
pXDest->stateTimer = 0;

aiPatrolSetMarker(pDest, pXDest);
if (spriteIsUnderwater(pDest)) aiPatrolState(pDest, kAiStatePatrolWaitW);
else aiPatrolState(pDest, kAiStatePatrolWaitL);
pXDest->data3 = 0; // reset the spot progress
if (aiPatrolSetMarker(pDest, pXDest))
{
if (spriteIsUnderwater(pDest))
aiPatrolState(pDest, kAiStatePatrolWaitW);
else
aiPatrolState(pDest, kAiStatePatrolWaitL);
pXDest->data3 = 0; // reset the spot progress
}
else
aiPatrolStop(pDest, -1);
}
}

Expand Down Expand Up @@ -7393,15 +7474,13 @@ void aiPatrolThink(spritetype* pSprite, XSPRITE* pXSprite) {

}

// release the enemy
if (isFinal) {
// release the enemy or move the next marker
if (isFinal || !aiPatrolSetMarker(pSprite, pXSprite))
{
aiPatrolStop(pSprite, -1);
return;
}

// move next marker
aiPatrolSetMarker(pSprite, pXSprite);

} else if (aiPatrolTurning(pXSprite->aiState)) {

//viewSetSystemMessage("TURN");
Expand Down Expand Up @@ -7507,15 +7586,12 @@ void aiPatrolThink(spritetype* pSprite, XSPRITE* pXSprite) {
}
}

// release the enemy
if (isFinal) {
// release the enemy or move the next marker
if (isFinal || !aiPatrolSetMarker(pSprite, pXSprite))
{
aiPatrolStop(pSprite, -1);
return;
}

// move the next marker
aiPatrolSetMarker(pSprite, pXSprite);

}

}
Expand Down
2 changes: 1 addition & 1 deletion source/blood/src/nnexts.h
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ void killEvents(int nRx, int nCmd);
void changeSpriteAngle(spritetype* pSpr, int nAng);
int getVelocityAngle(spritetype* pSpr);
// ------------------------------------------------------------------------- //
void aiPatrolSetMarker(spritetype* pSprite, XSPRITE* pXSprite);
char aiPatrolSetMarker(spritetype* pSprite, XSPRITE* pXSprite);
void aiPatrolThink(spritetype* pSprite, XSPRITE* pXSprite);
void aiPatrolStop(spritetype* pSprite, int target, bool alarm = false);
void aiPatrolAlarmFull(spritetype* pSprite, XSPRITE* pXTarget, bool chain);
Expand Down

5 comments on commit 92eebf5

@DanielGibson
Copy link
Contributor

Choose a reason for hiding this comment

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

Note: moving the call to SeqLoadSaveConstruct() up breaks loading old savegames.

I only tested this with the patched NBlood version of What Lies Beneath and my custom branch that rebases the WLB changes to the latest code (I'll hopefully release that soon), but I'd be surprised if this didn't affect the base game as well.

Is there a way to detect old savegames, so one could call SeqLoadSaveConstruct() after PlayerLoadSaveConstruct(); inestead of at the beginning, (only) in that case?

@NoOneBlood
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I doubt you can detect old savegames (if you really need to), unless you have nblood build info

@DanielGibson
Copy link
Contributor

@DanielGibson DanielGibson commented on 92eebf5 Dec 15, 2024

Choose a reason for hiding this comment

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

Thanks for your reply!
I investigated a bit more, and it would be possible by using the BYTEVERSION define from common_game.h, which should be be increased each time the savegame format is broken.

I implemented loading older savegames in DanielGibson@7b50d10, which increased BYTEVERSION and can load savegames taken with the nblood version shipped with WLB. Doing this in normal NBlood is probably not possible, because with that change loading savegames created after your commit (that still use BYTEVERSION 105) doesn't work.

In case anyone is interested in the other changes from and for WLB, see the whole branch: https://github.com/DanielGibson/NBlood/commits/wlb-changes-rb/

The original WLB source as released by BloodyTom is pretty much this: DanielGibson@efd9f32
(he released a zip with the source and I figured out what commit it was based on and applied the changes there)

@Hendricks266
Copy link
Collaborator

@Hendricks266 Hendricks266 commented on 92eebf5 Dec 15, 2024

Choose a reason for hiding this comment

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

@NoOneBlood If you do something that breaks savegames, you need to bump BYTEVERSION. Or just don't break them haphazardly.

@DanielGibson Thanks for shepherding the WLB changes. We NBlood developers do not want mod-specific forks to exist, because it inconveniences non-Windows users, and inhibits players from benefiting from subsequent updates the port receives. I wish modders such as BloodyTom would reach out to us during development before publishing their work that way. For now, if you could help them prepare a standard .diff/.patch file with the changes and include it with the mod itself, that would make things a bit easier for everyone. (It would also be good to clean up the diff, such as by surrounding disabled code with #if 0 rather than adding // to every line.) Long-term, we can investigate what modding capabilities the port needs so no exe changes are needed at all.

@DanielGibson
Copy link
Contributor

Choose a reason for hiding this comment

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

FWIW, the change @NoOneBlood did there made a lot of sense as far as I can tell from the comment (I'm not very familiar with how Blood or the Build Engine in general works), but yeah, the BYTEVERSION should've been bumped.

I think WLB has four different kinds of changes:

  1. Increase the maximum amount of maps from 16 to 45 (kMaxLevels) - I assume NBlood could just do the same change without breaking the base game or other mods (of course I can't be sure as I'm not very familiar with the source). Maybe just increase it to 64 while you're at it.
    • At one place (zLevelNames[][] in blood/src/menu.cpp) 16 was hardcoded instead of using kMaxLevels. That caused an assertion to trigger that BloodyTom just commented out, I fixed it properly by using kMaxLevels there.
  2. Disable some NOONE_EXTENSIONS code (that's most of the commented out stuff). This could be hidden behind #ifdef BLOOD_WLB or similar?
  3. Some other changes to gamecode, like disabling some powerups, changing some sprite sizes/scales in function calls to 0 (playerSizeShrink(), playerSizeGrow(), call to actFireVector() in FirePitchfork()`).
    • No idea how many of those are strictly necessary to make WLB work (why should it matter if code for powerups exists that aren't used in the mod? could be related to replacing strings for those with ones for new items in db.cpp though) or if they should be done differently (should sprite size/scale really be set to 0, or should the sprite not be created in the first place? I don't know what exactly he was trying to do there).
      For those that are necessary, I'm not sure what NBlood could do to allow mods achieving the same without changing the code.
      For now I could hide them behind #ifdef BLOOD_WLB
  4. Changing things that are basically game data: strings in db.cpp, values in dude.cpp (and the icon and image used by the launcher, but those are negligible).
    • You could allow mods to load the strings and maybe also the values (whatever they do, I have no idea) from text files. Of course I don't know how that text and those values relate to the rest of the code, maybe adding stuff there breaks other code if it isn't adjusted as well, no idea.
    • For now I could hide these changes behind #ifdef BLOOD_WLB

I'll try to get in touch with BloodyTom and ask him if he wants my fixes and if he's interested in handling this better in the future, by cooperating with you.
As far as I know (I think it was mentioned in the README?) he is no programmer, which explains why his changes are a bit hacky - and it also means he probably doesn't know what a diff is or how to create one.

Would you be interested to get the changes for WLB + my fixes upstream, with the WLB-specific code hidden behind #ifdef BLOOD_WLB so they're only compiled in when building with make BLOOD_WLB=1 or similar?

By the way, regarding non-Windows platforms, I couldn't use the Linux binary provided by BloodyTome because my distro provides different versions of libflac and libvpx than the ones he built against.
libvpx can thankfully be disabled with USE_LIBVPX=0 (and isn't used by WLB anyway), but libflac can't be disabled. I'd suggest using the single-header lib dr_flac.h instead: https://github.com/mackron/dr_libs

Please sign in to comment.