diff --git a/src/d_main.c b/src/d_main.c index e2e342d8d..1d07c9298 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -1196,8 +1196,8 @@ static void IdentifyVersion(void) D_AddFile(startupiwads, va(pandf,srb2waddir,"textures_segazones.pk3")); D_AddFile(startupiwads, va(pandf,srb2waddir,"textures_originalzones.pk3")); D_AddFile(startupiwads, va(pandf,srb2waddir,"chars.pk3")); - D_AddFile(startupiwads, va(pandf,srb2waddir,"maps.pk3")); D_AddFile(startupiwads, va(pandf,srb2waddir,"followers.pk3")); + D_AddFile(startupiwads, va(pandf,srb2waddir,"maps.pk3")); #define UNLOCKTESTING #if defined(DEVELOP) && defined(UNLOCKTESTING) D_AddFile(startupiwads, va(pandf,srb2waddir,"unlocks.pk3")); @@ -1531,8 +1531,8 @@ void D_SRB2Main(void) mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_TEXTURES_SEGA_PK3); // textures_segazones.pk3 mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_TEXTURES_ORIGINAL_PK3); // textures_originalzones.pk3 mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_CHARS_PK3); // chars.pk3 + mainwads++; W_VerifyFileMd5(mainwads, ASSET_HASH_FOLLOWERS_PK3); // followers.pk3 mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_MAPS_PK3); // maps.pk3 - mainwads++; W_VerifyFileMd5(mainwads, ASSET_HASH_FOLLOWERS_PK3); // followers.pk3 #else #ifdef USE_PATCH_FILE mainwads++; // scripts.pk3 @@ -1542,8 +1542,8 @@ void D_SRB2Main(void) mainwads++; // textures_segazones.pk3 mainwads++; // textures_originalzones.pk3 mainwads++; // chars.pk3 - mainwads++; // maps.pk3 mainwads++; // followers.pk3 + mainwads++; // maps.pk3 #ifdef UNLOCKTESTING mainwads++; // unlocks.pk3 #endif diff --git a/src/deh_soc.c b/src/deh_soc.c index fea7df30e..751c3c07a 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -143,6 +143,9 @@ void clear_emblems(void) Z_Free(emblemlocations[i].stringVar); emblemlocations[i].stringVar = NULL; + + Z_Free(emblemlocations[i].stringVar2); + emblemlocations[i].stringVar2 = NULL; } memset(&emblemlocations, 0, sizeof(emblemlocations)); @@ -187,7 +190,7 @@ void clear_levels(void) // (no need to set num to 0, we're freeing the entire header shortly) Z_Free(mapheaderinfo[nummapheaders]->customopts); - P_DeleteFlickies(nummapheaders); + P_DeleteHeaderFollowers(nummapheaders); Z_Free(mapheaderinfo[nummapheaders]->mainrecord); @@ -1042,77 +1045,47 @@ void readlevelheader(MYFILE *f, char * name) // Now go to uppercase strupr(word2); - // List of flickies that are be freed in this map - if (fastcmp(word, "FLICKYLIST") || fastcmp(word, "ANIMALLIST")) + // List of followers that are be freed in this map + if (fastcmp(word, "FOLLOWERS")) { if (fastcmp(word2, "NONE")) - P_DeleteFlickies(num); - else if (fastcmp(word2, "DEMO")) - P_SetDemoFlickies(num); - else if (fastcmp(word2, "ALL")) - { - mobjtype_t tmpflickies[MAXFLICKIES]; - - for (mapheaderinfo[num]->numFlickies = 0; - ((mapheaderinfo[num]->numFlickies < MAXFLICKIES) && FLICKYTYPES[mapheaderinfo[num]->numFlickies].type); - mapheaderinfo[num]->numFlickies++) - tmpflickies[mapheaderinfo[num]->numFlickies] = FLICKYTYPES[mapheaderinfo[num]->numFlickies].type; - - if (mapheaderinfo[num]->numFlickies) // just in case... - { - size_t newsize = sizeof(mobjtype_t) * mapheaderinfo[num]->numFlickies; - mapheaderinfo[num]->flickies = Z_Realloc(mapheaderinfo[num]->flickies, newsize, PU_STATIC, NULL); - M_Memcpy(mapheaderinfo[num]->flickies, tmpflickies, newsize); - } - } + P_DeleteHeaderFollowers(num); + else if (fastcmp(word2, "DEFAULT")) + P_SetDefaultHeaderFollowers(num); else { - mobjtype_t tmpflickies[MAXFLICKIES]; - mapheaderinfo[num]->numFlickies = 0; + INT16 tmpfollowers[MAXHEADERFOLLOWERS]; + mapheaderinfo[num]->numFollowers = 0; tmp = strtok(word2,","); - // get up to the first MAXFLICKIES flickies + // get up to the first MAXHEADERFOLLOWERS followers do { - if (mapheaderinfo[num]->numFlickies == MAXFLICKIES) // never going to get above that number + if (mapheaderinfo[num]->numFollowers == MAXHEADERFOLLOWERS) // never going to get above that number { - deh_warning("Level header %d: too many flickies\n", num); + deh_warning("Level header %d: too many followers\n", num); break; } - if (fastncmp(tmp, "MT_", 3)) // support for specified mobjtypes... { - i = get_mobjtype(tmp); - if (!i) + i = K_FollowerAvailable(tmp); + if (i == -1) { - //deh_warning("Level header %d: unknown flicky mobj type %s\n", num, tmp); -- no need for this line as get_mobjtype complains too + deh_warning("Level header %d: unknown follower selection %s\n", num, tmp); continue; } - tmpflickies[mapheaderinfo[num]->numFlickies] = i; + tmpfollowers[mapheaderinfo[num]->numFollowers] = i; + mapheaderinfo[num]->numFollowers++; } - else // ...or a quick, limited selection of default flickies! - { - for (i = 0; FLICKYTYPES[i].name; i++) - if (fastcmp(tmp, FLICKYTYPES[i].name)) - break; - - if (!FLICKYTYPES[i].name) - { - deh_warning("Level header %d: unknown flicky selection %s\n", num, tmp); - continue; - } - tmpflickies[mapheaderinfo[num]->numFlickies] = FLICKYTYPES[i].type; - } - mapheaderinfo[num]->numFlickies++; } while ((tmp = strtok(NULL,",")) != NULL); - if (mapheaderinfo[num]->numFlickies) + if (mapheaderinfo[num]->numFollowers) { - size_t newsize = sizeof(mobjtype_t) * mapheaderinfo[num]->numFlickies; - mapheaderinfo[num]->flickies = Z_Realloc(mapheaderinfo[num]->flickies, newsize, PU_STATIC, NULL); + size_t newsize = sizeof(INT16) * mapheaderinfo[num]->numFollowers; + mapheaderinfo[num]->followers = Z_Realloc(mapheaderinfo[num]->followers, newsize, PU_STATIC, NULL); // now we add them to the list! - M_Memcpy(mapheaderinfo[num]->flickies, tmpflickies, newsize); + M_Memcpy(mapheaderinfo[num]->followers, tmpfollowers, newsize); } else - deh_warning("Level header %d: no valid flicky types found\n", num); + deh_warning("Level header %d: no valid follower types found\n", num); } } @@ -2231,6 +2204,13 @@ void reademblemdata(MYFILE *f, INT32 num) emblemlocations[num-1].var = get_number(word2); } + else if (fastcmp(word, "VAR2")) + { + Z_Free(emblemlocations[num-1].stringVar2); + emblemlocations[num-1].stringVar2 = Z_StrDup(word2); + + emblemlocations[num-1].var2 = get_number(word2); + } else if (fastcmp(word, "FLAGS")) emblemlocations[num-1].flags = get_number(word2); else diff --git a/src/deh_tables.c b/src/deh_tables.c index b5746272d..5b2fa3f24 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -31,31 +31,6 @@ char *FREE_MOBJS[NUMMOBJFREESLOTS]; char *FREE_SKINCOLORS[NUMCOLORFREESLOTS]; UINT8 used_spr[(NUMSPRITEFREESLOTS / 8) + 1]; // Bitwise flag for sprite freeslot in use! I would use ceil() here if I could, but it only saves 1 byte of memory anyway. -struct flickytypes_s FLICKYTYPES[] = { - {"BLUEBIRD", MT_FLICKY_01}, // Flicky (Flicky) - {"RABBIT", MT_FLICKY_02}, // Pocky (1) - {"CHICKEN", MT_FLICKY_03}, // Cucky (1) - {"SEAL", MT_FLICKY_04}, // Rocky (1) - {"PIG", MT_FLICKY_05}, // Picky (1) - {"CHIPMUNK", MT_FLICKY_06}, // Ricky (1) - {"PENGUIN", MT_FLICKY_07}, // Pecky (1) - {"FISH", MT_FLICKY_08}, // Nicky (CD) - {"RAM", MT_FLICKY_09}, // Flocky (CD) - {"PUFFIN", MT_FLICKY_10}, // Wicky (CD) - {"COW", MT_FLICKY_11}, // Macky (SRB2) - {"RAT", MT_FLICKY_12}, // Micky (2) - {"BEAR", MT_FLICKY_13}, // Becky (2) - {"DOVE", MT_FLICKY_14}, // Docky (CD) - {"CAT", MT_FLICKY_15}, // Nyannyan (Flicky) - {"CANARY", MT_FLICKY_16}, // Lucky (CD) - {"a", 0}, // End of normal flickies - a lower case character so will never fastcmp valid with uppercase tmp - //{"FLICKER", MT_FLICKER}, // Flacky (SRB2) - {"SPIDER", MT_SECRETFLICKY_01}, // Sticky (SRB2) - {"BAT", MT_SECRETFLICKY_02}, // Backy (SRB2) - {"SEED", MT_SEED}, // Seed (CD) - {NULL, 0} -}; - // IMPORTANT! // DO NOT FORGET TO SYNC THIS LIST WITH THE ACTIONNUM ENUM IN INFO.H actionpointer_t actionpointers[] = @@ -3885,14 +3860,6 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_DEZLASER_TRAIL4", "S_DEZLASER_TRAIL5", - // Audience Members - "S_RANDOMAUDIENCE", - "S_AUDIENCE_CHAO_CHEER1", - "S_AUDIENCE_CHAO_CHEER2", - "S_AUDIENCE_CHAO_WIN1", - "S_AUDIENCE_CHAO_WIN2", - "S_AUDIENCE_CHAO_LOSE", - // 1.0 Kart Decoratives "S_FLAYM1", "S_FLAYM2", @@ -6428,6 +6395,7 @@ struct int_const_s const INT_CONST[] = { // Global emblem var flags {"GE_NOTMEDAL", GE_NOTMEDAL}, {"GE_TIMED", GE_TIMED}, + {"GE_FOLLOWER", GE_FOLLOWER}, // Map emblem var flags {"ME_ENCORE",ME_ENCORE}, diff --git a/src/deh_tables.h b/src/deh_tables.h index 816f09c8f..79e0fd8f6 100644 --- a/src/deh_tables.h +++ b/src/deh_tables.h @@ -41,8 +41,6 @@ struct flickytypes_s { const mobjtype_t type; }; -#define MAXFLICKIES 64 - /** Action pointer for reading actions from Dehacked lumps. */ struct actionpointer_t diff --git a/src/doomstat.h b/src/doomstat.h index 72a3a8c71..f7e13c93b 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -386,6 +386,7 @@ struct staffbrief_t }; #define MAXMUSNAMES 3 // maximum definable music tracks per level +#define MAXHEADERFOLLOWERS 32 /** Map header information. */ @@ -458,9 +459,9 @@ struct mapheader_t boolean use_light_angle; ///< When false, wall lighting is evenly distributed. When true, wall lighting is directional. angle_t light_angle; ///< Angle of directional wall lighting. - // Freed animal information - UINT8 numFlickies; ///< Internal. For freed flicky support. - mobjtype_t *flickies; ///< List of freeable flickies in this level. Allocated dynamically for space reasons. Be careful. + // Audience information + UINT8 numFollowers; ///< Internal. For audience support. + INT16 *followers; ///< List of audience followers in this level. Allocated dynamically for space reasons. Be careful. // Script information char runsoc[33]; ///< SOC to execute at start of level (32 character limit instead of 63) diff --git a/src/info.c b/src/info.c index cfa23907a..505ad5373 100644 --- a/src/info.c +++ b/src/info.c @@ -4483,16 +4483,6 @@ state_t states[NUMSTATES] = {SPR_DEZL, FF_FULLBRIGHT|2, 2, {NULL}, 0, 0, S_DEZLASER_TRAIL5}, // S_DEZLASER_TRAIL4 {SPR_DEZL, FF_FULLBRIGHT|1, 2, {NULL}, 0, 0, S_NULL}, // S_DEZLASER_TRAIL5 - {SPR_NULL, 0, 1, {A_RandomStateRange}, S_AUDIENCE_CHAO_CHEER1, S_AUDIENCE_CHAO_CHEER2, S_RANDOMAUDIENCE}, // S_RANDOMAUDIENCE - - {SPR_AUDI, 0, 5, {NULL}, 0, 0, S_AUDIENCE_CHAO_CHEER2}, // S_AUDIENCE_CHAO_CHEER1 - {SPR_AUDI, 1, 20, {A_BunnyHop}, 7, 0, S_AUDIENCE_CHAO_CHEER1}, // S_AUDIENCE_CHAO_CHEER2 - - {SPR_AUDI, 2, 5, {NULL}, 0, 0, S_AUDIENCE_CHAO_WIN2}, // S_AUDIENCE_CHAO_WIN1 - {SPR_AUDI, 3, 25, {A_BunnyHop}, 10, 0, S_AUDIENCE_CHAO_WIN1}, // S_AUDIENCE_CHAO_WIN2 - - {SPR_AUDI, 4|FF_ANIMATE, -1, {NULL}, 1, 17, S_NULL}, // S_AUDIENCE_CHAO_LOSE - {SPR_FLAM, FF_FULLBRIGHT|FF_ADD|0, 3, {NULL}, 0, 0, S_FLAYM2}, // S_FLAYM1, {SPR_FLAM, FF_FULLBRIGHT|FF_ADD|1, 3, {NULL}, 0, 0, S_FLAYM3}, // S_FLAYM2, {SPR_FLAM, FF_FULLBRIGHT|FF_ADD|2, 3, {NULL}, 0, 0, S_FLAYM4}, // S_FLAYM3, @@ -24880,7 +24870,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] = { // MT_RANDOMAUDIENCE 1488, // doomednum - S_RANDOMAUDIENCE, // spawnstate + S_UNKNOWN, // spawnstate 1000, // spawnhealth S_NULL, // seestate sfx_None, // seesound diff --git a/src/info.h b/src/info.h index d80c9798a..3a73a5280 100644 --- a/src/info.h +++ b/src/info.h @@ -4929,14 +4929,6 @@ typedef enum state S_DEZLASER_TRAIL4, S_DEZLASER_TRAIL5, - // Audience Members - S_RANDOMAUDIENCE, - S_AUDIENCE_CHAO_CHEER1, - S_AUDIENCE_CHAO_CHEER2, - S_AUDIENCE_CHAO_WIN1, - S_AUDIENCE_CHAO_WIN2, - S_AUDIENCE_CHAO_LOSE, - // 1.0 Kart Decoratives S_FLAYM1, S_FLAYM2, diff --git a/src/k_follower.c b/src/k_follower.c index f0a1d7748..daa7b9d0d 100644 --- a/src/k_follower.c +++ b/src/k_follower.c @@ -369,7 +369,10 @@ void K_HandleFollower(player_t *player) { // finally, add a cool floating effect to the z height. // not stolen from k_kart I swear!! - fixed_t sine = FixedMul(fl.bobamp, FINESINE(((FixedMul(4 * M_TAU_FIXED, fl.bobspeed) * leveltime) >> ANGLETOFINESHIFT) & FINEMASK)); + fixed_t sine = FixedMul(fl.bobamp, + FINESINE((( + FixedMul(4 * M_TAU_FIXED, fl.bobspeed) * leveltime + ) >> ANGLETOFINESHIFT) & FINEMASK)); sz += FixedMul(player->mo->scale, sine) * P_MobjFlip(player->mo); break; } diff --git a/src/k_objects.h b/src/k_objects.h index 875f177a8..0852100cc 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -114,6 +114,10 @@ void Obj_PlayerUsedRingShooter(mobj_t *base, player_t *player); void Obj_RingShooterDelete(mobj_t *mo); void Obj_UpdateRingShooterFace(mobj_t *part); +/* Follower Audience */ +void Obj_AudienceInit(mobj_t * mobj, mapthing_t *mthing, INT32 followerpick); +void Obj_AudienceThink(mobj_t * mobj, boolean focusonplayer); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/m_cond.h b/src/m_cond.h index a53e66ad7..9fdb5bd2e 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -132,6 +132,7 @@ struct conditionset_t // Global emblem flags #define GE_NOTMEDAL 1 // Doesn't count towards number of medals #define GE_TIMED 2 // Disappears after var time +#define GE_FOLLOWER 4 // Takes on the appearance of a Follower in (string)var2 // Map emblem flags #define ME_ENCORE 1 // Achieve in Encore @@ -147,7 +148,9 @@ struct emblem_t UINT16 color; ///< skincolor to use INT32 flags; ///< GE or ME constants INT32 var; ///< If needed, specifies extra information + INT32 var2; ///< Ditto char *stringVar; ///< String version + char *stringVar2; ///< Ditto }; // Unlockable information diff --git a/src/m_random.h b/src/m_random.h index 786f5d053..febfd4689 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -53,6 +53,8 @@ typedef enum PR_VOICES, // Player voice sounds PR_RANDOMSKIN, // Random skin select from Heavy Magician(?) + PR_RANDOMAUDIENCE, // Audience randomisation + PR_RULESCRAMBLE, // Rule scrambing events PR_MUSICSELECT, // Randomized music selection diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 609f18de4..6442860cc 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -15,4 +15,5 @@ target_sources(SRB2SDL2 PRIVATE loops.c drop-target.c ring-shooter.c + audience.c ) diff --git a/src/objects/audience.c b/src/objects/audience.c new file mode 100644 index 000000000..631429d05 --- /dev/null +++ b/src/objects/audience.c @@ -0,0 +1,308 @@ +#include "../doomdef.h" +#include "../info.h" +#include "../g_game.h" +#include "../k_objects.h" +#include "../k_follower.h" +#include "../m_random.h" +#include "../p_local.h" +#include "../p_setup.h" +#include "../r_draw.h" // R_GetColorByName +#include "../r_main.h" // R_PointToAngle2, R_PointToDist2 +#include "../z_zone.h" // Z_StrDup/Z_Free + +// The following cannot be used due to conflicts with MT_EMBLEM. +//#define audience_emblem_reserved_1(o) ((o)->reactiontime) + +#define audience_mainstate(o) ((o)->cvmem) + +#define audience_bobamp(o) ((o)->movefactor) +#define audience_bobspeed(o) ((o)->cusval) + +#define audience_animoffset(o) ((o)->threshold) + +#define audience_focusplayer(o) ((o)->lastlook) +#define audience_focusdelay(o) ((o)->movecount) + +void +Obj_AudienceInit +( mobj_t * mobj, + mapthing_t *mthing, + INT32 followerpick) +{ + INT16 *reflist = NULL; + INT16 tempreflist[MAXHEADERFOLLOWERS]; + UINT8 numref = 0; + + audience_mainstate(mobj) = S_NULL; + + // Pick follower + if (mthing != NULL) + { + if (mthing->stringargs[0] != NULL) + { + // From mapthing + char *stringcopy = Z_StrDup(mthing->stringargs[0]); + char *tok = strtok(stringcopy, " ,"); + char *c; // for erasing underscores + + numref = 0; + while (tok && numref < MAXHEADERFOLLOWERS) + { + // Match follower name conversion + for (c = tok; *c; c++) + { + if (*c != '_') + continue; + *c = ' '; + } + + if ((tempreflist[numref++] = K_FollowerAvailable(tok)) == -1) + CONS_Alert(CONS_WARNING, "Mapthing %s: Follower \"%s\" is invalid!\n", sizeu1(mthing-mapthings), tok); + tok = strtok(NULL, " ,"); + } + + Z_Free(stringcopy); + + reflist = tempreflist; + } + else + { + // From mapheader + + if (!mapheaderinfo[gamemap-1]) + { + // No mapheader, no shoes, no service. + return; + } + + numref = mapheaderinfo[gamemap-1]->numFollowers; + reflist = mapheaderinfo[gamemap-1]->followers; + } + + if (!numref || !reflist) + { + // This is the one thing a user should definitely be told about. + CONS_Alert(CONS_WARNING, "Mapthing %s: Follower audience has no valid followers to pick from!\n", sizeu1(mthing-mapthings)); + return; + } + + followerpick = reflist[P_RandomKey(PR_RANDOMAUDIENCE, numref)]; + + if (followerpick < 0 || followerpick >= numfollowers) + { + // Is this user error or user choice..? + P_RemoveMobj(mobj); + return; + } + } + + // Handle storing follower properties on the object + { + mobj->destscale = FixedMul(3*mobj->destscale, followers[followerpick].scale); + P_SetScale(mobj, mobj->destscale); + + audience_mainstate(mobj) = followers[followerpick].followstate; + + P_SetMobjState(mobj, audience_mainstate(mobj)); + if (P_MobjWasRemoved(mobj)) + return; + + // The following is derived from the default bobamp + if (mobj->type != MT_EMBLEM && !(mobj->flags & MF_NOGRAVITY) && followers[followerpick].bobamp < 4*FRACUNIT) + { + audience_bobamp(mobj) = 4*mobj->scale; + } + else + { + audience_bobamp(mobj) = FixedMul(mobj->scale, followers[followerpick].bobamp); + } + + audience_bobspeed(mobj) = followers[followerpick].bobspeed; + audience_focusplayer(mobj) = MAXPLAYERS; + + if (P_RandomChance(PR_RANDOMAUDIENCE, FRACUNIT/2)) + { + audience_animoffset(mobj) = 5; + } + } + + // Handle colors + if (mthing != NULL) + { + UINT16 colorpick = SKINCOLOR_NONE; + + if (mthing->stringargs[1] != NULL) + { + if (!stricmp("Random", mthing->stringargs[1])) + { + colorpick = FOLLOWERCOLOR_MATCH; + } + else + { + char *stringcopy = Z_StrDup(mthing->stringargs[1]); + char *tok = strtok(stringcopy, " "); + + numref = 0; + while (tok && numref < MAXHEADERFOLLOWERS) + { + if ((tempreflist[numref++] = R_GetColorByName(tok)) == SKINCOLOR_NONE) + CONS_Alert(CONS_WARNING, "Mapthing %s: Follower color \"%s\" is invalid!\n", sizeu1(mthing-mapthings), tok); + tok = strtok(NULL, " "); + } + + Z_Free(stringcopy); + + if (numref) + { + colorpick = tempreflist[P_RandomKey(PR_RANDOMAUDIENCE, numref)]; + } + } + } + + if (colorpick == SKINCOLOR_NONE + || (colorpick >= numskincolors + && colorpick != FOLLOWERCOLOR_MATCH + && colorpick != FOLLOWERCOLOR_OPPOSITE)) + { + colorpick = followers[followerpick].defaultcolor; + } + + if (colorpick >= numskincolors) + { + colorpick = P_RandomKey(PR_RANDOMAUDIENCE, numskincolors-1)+1; + } + + mobj->color = colorpick; + } +} + +void +Obj_AudienceThink +( mobj_t * mobj, + boolean focusonplayer) +{ + if (audience_mainstate(mobj) == S_NULL) + { + // Uninitialised, don't do anything funny. + return; + } + + if (focusonplayer == true) + { + if (audience_focusdelay(mobj) == 0) + { + fixed_t bestdist = INT32_MAX, dist; + UINT8 i; + + audience_focusplayer(mobj) = MAXPLAYERS; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false + || players[i].spectator == true + || P_MobjWasRemoved(players[i].mo)) + continue; + + dist = R_PointToDist2( + mobj->x, + mobj->y, + players[i].mo->x, + players[i].mo->y + ); + + if (dist >= bestdist) + continue; + + dist = R_PointToDist2( + mobj->z, + 0, + players[i].mo->z, + dist + ); + + if (dist >= bestdist) + continue; + + bestdist = dist; + audience_focusplayer(mobj) = i; + } + + // Try to add some spacing out so the object isn't constantly looking for players + audience_focusdelay(mobj) = TICRATE + min((bestdist/FRACUNIT), (2*TICRATE)) + (bestdist % TICRATE); + } + else + { + audience_focusdelay(mobj)--; + } + + if (audience_focusplayer(mobj) < MAXPLAYERS && audience_focusplayer(mobj) >= 0) + { + angle_t diff = R_PointToAngle2( + mobj->x, + mobj->y, + players[audience_focusplayer(mobj)].mo->x, + players[audience_focusplayer(mobj)].mo->y + ) - mobj->angle; + + boolean reverse = (diff >= ANGLE_180); + + if (reverse) + diff = InvAngle(diff); + + if (diff > (ANG1*5)) + diff /= 5; + + if (reverse) + diff = InvAngle(diff); + + mobj->angle += diff; + } + } + + if (mobj->flags & MF_NOGRAVITY) + { + // This horrible calculation was inherited from k_follower.c + mobj->sprzoff = FixedMul(audience_bobamp(mobj), + FINESINE((( + FixedMul(4 * M_TAU_FIXED, audience_bobspeed(mobj)) + * (leveltime + audience_animoffset(mobj)) + ) >> ANGLETOFINESHIFT) & FINEMASK)); + + // Offset to not go through floor... + if (mobj->type == MT_EMBLEM) + { + ; // ...unless it's important to keep a centered hitbox + } + else if (mobj->flags2 & MF2_OBJECTFLIP) + { + mobj->sprzoff -= audience_bobamp(mobj); + } + else + { + mobj->sprzoff += audience_bobamp(mobj); + } + } + else if (audience_animoffset(mobj) > 0) + { + // Skipped frames at spawn for offset in jumping + audience_animoffset(mobj)--; + } + else if (audience_bobamp(mobj) == 0) + { + // Just sit there + ; + } + else if (mobj->flags2 & MF2_OBJECTFLIP) + { + if (mobj->z + mobj->height >= mobj->ceilingz) + { + mobj->momz = -audience_bobamp(mobj); + P_SetMobjState(mobj, audience_mainstate(mobj)); + } + } + else if (mobj->z <= mobj->floorz) + { + mobj->momz = audience_bobamp(mobj); + P_SetMobjState(mobj, audience_mainstate(mobj)); + } +} diff --git a/src/p_enemy.c b/src/p_enemy.c index 59979ac9a..152ced8f3 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -10234,13 +10234,8 @@ mobj_t *P_InternalFlickySpawn(mobj_t *actor, mobjtype_t flickytype, fixed_t momz if (!flickytype) { - if (!mapheaderinfo[gamemap-1] || !mapheaderinfo[gamemap-1]->numFlickies) // No mapheader, no shoes, no service. - return NULL; - else - { - INT32 prandom = P_RandomKey(PR_UNDEFINED, mapheaderinfo[gamemap-1]->numFlickies); - flickytype = mapheaderinfo[gamemap-1]->flickies[prandom]; - } + // The flicky list system has been removed, so no backups are possible. + return NULL; } if (moveforward) diff --git a/src/p_mobj.c b/src/p_mobj.c index af2394298..1d7869812 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -40,6 +40,7 @@ #include "k_kart.h" #include "k_battle.h" #include "k_color.h" +#include "k_follower.h" #include "k_respawn.h" #include "k_bot.h" #include "k_terrain.h" @@ -7102,6 +7103,13 @@ static boolean P_MobjRegularThink(mobj_t *mobj) { INT32 trans = 0; + if (mobj->flags2 & MF2_STRONGBOX) + { + Obj_AudienceThink(mobj, true); + if (P_MobjWasRemoved(mobj)) + return false; + } + mobj->frame &= ~FF_TRANSMASK; mobj->renderflags &= ~RF_TRANSMASK; @@ -10267,39 +10275,9 @@ void P_SceneryThinker(mobj_t *mobj) mobj->renderflags &= ~RF_DONTDRAW; } - if (mobj->type != MT_RANDOMAUDIENCE) - return; - + if (mobj->type == MT_RANDOMAUDIENCE) { - if (!mobj->colorized) // a fan of someone? - return; - - if (mobj->threshold >= 0) // not already happy or sad? - { - if (!playeringame[mobj->threshold] || players[mobj->threshold].spectator) // focused on a valid player? - return; - - if (!(players[mobj->threshold].exiting) && !(players[mobj->threshold].pflags & PF_NOCONTEST)) // not finished yet? - return; - - if (K_IsPlayerLosing(&players[mobj->threshold])) - mobj->threshold = -2; - else - { - mobj->threshold = -1; - S_StartSound(mobj, sfx_chaooo); - } - } - - if (mobj->threshold == -1) - mobj->angle += ANGLE_22h; - - if (((statenum_t)(mobj->state-states) != S_AUDIENCE_CHAO_CHEER2) || (mobj->tics != states[S_AUDIENCE_CHAO_CHEER2].tics)) // not at the start of your cheer jump? - return; - - mobj->momz = 0; - - P_SetMobjState(mobj, ((mobj->threshold == -1) ? S_AUDIENCE_CHAO_WIN2 : S_AUDIENCE_CHAO_LOSE)); + Obj_AudienceThink(mobj, !!(mobj->flags2 & MF2_AMBUSH)); } } @@ -10814,41 +10792,6 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) case MT_CDUFO: P_SetScale(mobj, (mobj->destscale = 3*FRACUNIT/2)); break; - case MT_RANDOMAUDIENCE: - { - fixed_t randu = P_RandomFixed(PR_UNDEFINED); - P_SetScale(mobj, (mobj->destscale <<= 1)); - if (randu < (FRACUNIT/9)) // a fan of someone? - { - UINT8 i, pcount = 0; - UINT8 pnum[MAXPLAYERS]; - - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - continue; - pnum[pcount] = i; - pcount++; - } - - if (pcount) - { - mobj->threshold = pnum[P_RandomKey(PR_UNDEFINED, pcount)]; - mobj->color = players[mobj->threshold].skincolor; - mobj->colorized = true; - break; - } - } - - if (randu > (FRACUNIT/2)) - { - mobj->color = P_RandomKey(PR_UNDEFINED, numskincolors-1)+1; - break; - } - - mobj->color = SKINCOLOR_CYAN; - break; - } case MT_MARBLETORCH: P_SpawnMobj(mobj->x, mobj->y, mobj->z + (29*mobj->scale), MT_MARBLELIGHT); break; @@ -12309,10 +12252,11 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) INT32 j; emblem_t* emblem = M_GetLevelEmblems(gamemap); skincolornum_t emcolor; + INT16 tagnum = Tag_FGet(&mthing->tags); while (emblem) { - if (emblem->type == ET_GLOBAL && emblem->tag == Tag_FGet(&mthing->tags)) + if (emblem->type == ET_GLOBAL && emblem->tag == tagnum) break; emblem = M_GetLevelEmblems(-1); @@ -12320,7 +12264,7 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) if (!emblem) { - CONS_Alert(CONS_WARNING, "P_SetupEmblem: No map emblem for map %d with tag %d found!\n", gamemap, Tag_FGet(&mthing->tags)); + CONS_Alert(CONS_WARNING, "P_SetupEmblem: No map emblem for map %d with tag %d found!\n", gamemap, tagnum); return false; } @@ -12340,6 +12284,44 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) mobj->reactiontime = emblemlocations[j].var; } + if (emblemlocations[j].flags & GE_FOLLOWER) + { + INT32 followerpick; + char testname[SKINNAMESIZE+1]; + size_t i; + + // match deh_soc readfollower() + for (i = 0; emblemlocations[j].stringVar2[i]; i++) + { + testname[i] = emblemlocations[j].stringVar2[i]; + if (emblemlocations[j].stringVar2[i] == '_') + testname[i] = ' '; + } + testname[i] = '\0'; + followerpick = K_FollowerAvailable(testname); + + if (followerpick == -1) + { + CONS_Alert(CONS_WARNING, "P_SetupEmblem: Follower \"%s\" on emblem for map %d with tag %d not found!\n", emblemlocations[j].stringVar2, gamemap, tagnum); + return false; + } + + // Signal that you are to behave like a follower + mobj->flags2 |= MF2_STRONGBOX; + if (followers[followerpick].mode == FOLLOWERMODE_GROUND) + { + mobj->flags &= ~(MF_NOGRAVITY|MF_NOCLIPHEIGHT); + } + + // Set up data + Obj_AudienceInit(mobj, NULL, followerpick); + if (P_MobjWasRemoved(mobj)) + { + CONS_Alert(CONS_WARNING, "P_SetupEmblem: Follower \"%s\" causes emblem for map %d with tag %d to be removed immediately!\n", emblemlocations[j].stringVar2, gamemap, tagnum); + return false; + } + } + return true; } @@ -13217,6 +13199,25 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean } break; } + case MT_RANDOMAUDIENCE: + { + if (mthing->args[2] != 0) + { + mobj->flags |= MF_NOGRAVITY; + } + + if (mthing->args[3] != 0) + { + mobj->flags2 |= MF2_AMBUSH; + } + + Obj_AudienceInit(mobj, mthing, -1); + + if (P_MobjWasRemoved(mobj)) + return false; + + break; + } case MT_AAZTREE_HELPER: { fixed_t top = mobj->z; @@ -13420,6 +13421,9 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj, boolean break; } + if (P_MobjWasRemoved(mobj)) + return false; + if (mobj->flags & MF_BOSS) { if (mthing->args[1]) // No egg trap for this boss @@ -13441,7 +13445,12 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, mobj->destscale = FixedMul(mobj->destscale, mthing->scale); if (!P_SetupSpawnedMapThing(mthing, mobj, &doangle)) + { + if (P_MobjWasRemoved(mobj)) + return NULL; + return mobj; + } if (doangle) { @@ -13669,7 +13678,8 @@ static void P_SpawnItemRow(mapthing_t *mthing, mobjtype_t *itemtypes, UINT8 numi y + FixedMul(length, FINESINE(fineangle)), z, MT_LOOPCENTERPOINT); - Obj_LinkLoopAnchor(loopanchor, loopcenter, mthing->args[0]); + if (!P_MobjWasRemoved(loopanchor)) + Obj_LinkLoopAnchor(loopanchor, loopcenter, mthing->args[0]); } for (r = 0; r < numitems; r++) diff --git a/src/p_setup.c b/src/p_setup.c index 32f0766eb..dc0a66f7d 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -347,31 +347,51 @@ FUNCNORETURN static ATTRNORETURN void CorruptMapError(const char *msg) I_Error("Invalid or corrupt map.\nLook in log file or text console for technical details."); } -/** Sets a header's flickies to be equivalent to the original Freed Animals +/** Sets a header's followers to the default list * - * \param i The header to set flickies for + * \param i The header to set followers for */ -void P_SetDemoFlickies(INT16 i) +void P_SetDefaultHeaderFollowers(UINT16 i) { - mapheaderinfo[i]->numFlickies = 5; - mapheaderinfo[i]->flickies = Z_Realloc(mapheaderinfo[i]->flickies, 5*sizeof(mobjtype_t), PU_STATIC, NULL); - mapheaderinfo[i]->flickies[0] = MT_FLICKY_02/*MT_BUNNY*/; - mapheaderinfo[i]->flickies[1] = MT_FLICKY_01/*MT_BIRD*/; - mapheaderinfo[i]->flickies[2] = MT_FLICKY_12/*MT_MOUSE*/; - mapheaderinfo[i]->flickies[3] = MT_FLICKY_11/*MT_COW*/; - mapheaderinfo[i]->flickies[4] = MT_FLICKY_03/*MT_CHICKEN*/; + static INT16 defaultfollowers[MAXHEADERFOLLOWERS]; + static UINT8 validdefaultfollowers = 0; + + if (validdefaultfollowers == 0) + { + const char *defaultfollowernames[] = + { + "Flicky", + "Chao", + "Motobuddy", + NULL + }; + + for (validdefaultfollowers = 0; defaultfollowernames[validdefaultfollowers]; validdefaultfollowers++) + { + defaultfollowers[validdefaultfollowers] = K_FollowerAvailable(defaultfollowernames[validdefaultfollowers]); + } + + I_Assert(validdefaultfollowers != 0); + } + + mapheaderinfo[i]->followers = Z_Realloc(mapheaderinfo[i]->followers, sizeof(INT16) * validdefaultfollowers, PU_STATIC, NULL); + + for (mapheaderinfo[i]->numFollowers = 0; mapheaderinfo[i]->numFollowers < validdefaultfollowers; mapheaderinfo[i]->numFollowers++) + { + mapheaderinfo[i]->followers[mapheaderinfo[i]->numFollowers] = defaultfollowers[mapheaderinfo[i]->numFollowers]; + } } -/** Clears a header's flickies +/** Clears a header's followers * - * \param i The header to clear flickies for + * \param i The header to clear followers for */ -void P_DeleteFlickies(INT16 i) +void P_DeleteHeaderFollowers(UINT16 i) { - if (mapheaderinfo[i]->flickies) - Z_Free(mapheaderinfo[i]->flickies); - mapheaderinfo[i]->flickies = NULL; - mapheaderinfo[i]->numFlickies = 0; + if (mapheaderinfo[i]->followers) + Z_Free(mapheaderinfo[i]->followers); + mapheaderinfo[i]->followers = NULL; + mapheaderinfo[i]->numFollowers = 0; } #define NUMLAPS_DEFAULT 3 @@ -419,10 +439,10 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) mapheaderinfo[num]->light_contrast = 16; mapheaderinfo[num]->use_light_angle = false; mapheaderinfo[num]->light_angle = 0; -#if 1 // equivalent to "FlickyList = DEMO" - P_SetDemoFlickies(num); -#else // equivalent to "FlickyList = NONE" - P_DeleteFlickies(num); +#if 1 // equivalent to "Followers = DEFAULT" + P_SetDefaultHeaderFollowers(num); +#else + P_DeleteHeaderFollowers(num); #endif mapheaderinfo[num]->mapvisited = 0; @@ -486,7 +506,7 @@ void P_AllocMapHeader(INT16 i) mapheaderinfo[i]->ghostCount = 0; mapheaderinfo[i]->cup = NULL; mapheaderinfo[i]->mainrecord = NULL; - mapheaderinfo[i]->flickies = NULL; + mapheaderinfo[i]->followers = NULL; nummapheaders++; } P_ClearSingleMapHeaderInfo(i); @@ -1807,8 +1827,10 @@ static void ParseTextmapThingParameter(UINT32 i, const char *param, const char * size_t argnum = atol(param + 9); if (argnum >= NUMMAPTHINGSTRINGARGS) return; - mapthings[i].stringargs[argnum] = Z_Malloc(strlen(val) + 1, PU_LEVEL, NULL); - M_Memcpy(mapthings[i].stringargs[argnum], val, strlen(val) + 1); + size_t len = strlen(val); + mapthings[i].stringargs[argnum] = Z_Malloc(len + 1, PU_LEVEL, NULL); + M_Memcpy(mapthings[i].stringargs[argnum], val, len); + mapthings[i].stringargs[argnum][len] = '\0'; } else if (fastncmp(param, "arg", 3) && strlen(param) > 3) { @@ -6718,6 +6740,10 @@ static void P_ConvertBinaryThingTypes(void) case 1305: //Rollout Rock mapthings[i].args[0] = !!(mapthings[i].options & MTF_AMBUSH); break; + case 1488: // Follower Audience (unfortunately numbered) + mapthings[i].args[2] = !!(mapthings[i].options & MTF_OBJECTSPECIAL); + mapthings[i].args[3] = !!(mapthings[i].options & MTF_AMBUSH); + break; case 1500: //Glaregoyle case 1501: //Glaregoyle (Up) case 1502: //Glaregoyle (Down) diff --git a/src/p_setup.h b/src/p_setup.h index c743460ce..fd343f927 100644 --- a/src/p_setup.h +++ b/src/p_setup.h @@ -144,8 +144,8 @@ boolean P_ApplyLightOffsetFine(UINT8 baselightlevel); size_t P_PrecacheLevelFlats(void); void P_AllocMapHeader(INT16 i); -void P_SetDemoFlickies(INT16 i); -void P_DeleteFlickies(INT16 i); +void P_SetDefaultHeaderFollowers(UINT16 i); +void P_DeleteHeaderFollowers(UINT16 i); // Needed for NiGHTS void P_ReloadRings(void);