diff --git a/levels/gzr-rip/alf/baghdad-2.alf b/levels/gzr-rip/alf/baghdad-2.alf index cd42dcc8..5da71202 100644 --- a/levels/gzr-rip/alf/baghdad-2.alf +++ b/levels/gzr-rip/alf/baghdad-2.alf @@ -74,4 +74,4 @@ - \ No newline at end of file + diff --git a/src/game/CAbstractPlayer.cpp b/src/game/CAbstractPlayer.cpp index 34a438fa..ce1ac94b 100644 --- a/src/game/CAbstractPlayer.cpp +++ b/src/game/CAbstractPlayer.cpp @@ -1820,40 +1820,104 @@ void CAbstractPlayer::IncarnateSound() { gHub->ReleaseLink(aLink); } -void CAbstractPlayer::Reincarnate() { - CIncarnator *placeList; - long bestCount = LONG_MAX; +void CAbstractPlayer::Incarnate() { + // for the initial spawn use the simple "usage" ordering + // note: this value is updated with the server's setting after the initial call (see CNetManager::DoConfig()) + itsGame->spawnOrder = ksUsage; + Reincarnate(); +} - // first, determine the count for the least-visited Incarnator - for (placeList = itsGame->incarnatorList; placeList != nullptr; placeList = placeList->nextIncarnator) { - if (placeList->enabled && (placeList->colorMask & teamMask) && (placeList->useCount < bestCount)) { - bestCount = placeList->useCount; +Fixed CAbstractPlayer::ClosestOpponentDistance(Vector &location) { + Fixed minDist = MAXFIXED; + DBG_Log("spawn", " Finding closest OPPONENT to INCARN"); + + for (int i = 0; i < kMaxAvaraPlayers; i++) { + if(i != itsManager->Slot()) { + CAbstractPlayer* player = itsGame->itsNet->playerTable[i]->GetPlayer(); + if (player != NULL && !player->isOut && teamMask != player->teamMask) { + DBG_Log("spawn", " OPPONENT[%d] LOC= %s", i, FormatVectorFloat(player->location).c_str()); + + if(i != itsManager->Slot()) { + Fixed d = FDistanceEstimate(player->location, location); + DBG_Log("spawn", " dist= %.4f", ToFloat(d)); + if (d < minDist) { + minDist = d; + DBG_Log("spawn", " CLOSEST OPPONENT, dist= %.4f", ToFloat(d)); + } + } + } } } + return minDist; +} - // try the least-visited Incarnators until one works - for (placeList = itsGame->incarnatorList; placeList != nullptr; placeList = placeList->nextIncarnator) { - if (placeList->enabled && (placeList->colorMask & teamMask) && (placeList->useCount == bestCount)) { - if (itsManager->Presence() != kzSpectating && ReincarnateComplete(placeList)) { - break; +void CAbstractPlayer::Reincarnate() { + std::list sortedIncarnators; + Fixed furthest = MINFIXED; + + DBG_Log("spawn", "Reincarnate() SLOT= %d, ORDER = %d", itsManager->Slot(), itsGame->spawnOrder); + + for (CIncarnator *incarnator = itsGame->incarnatorList; incarnator != nullptr; incarnator = incarnator->nextIncarnator) { + if (incarnator->enabled && (incarnator->colorMask & teamMask)) { //} && incarnator->useCount == 0) { + DBG_Log("spawn", "\n"); + DBG_Log("spawn", "INCARN LOC= %s", FormatVectorFloat(incarnator->location).c_str()); + + if (itsGame->spawnOrder == ksDistance || itsGame->spawnOrder == ksHybrid) { + Fixed minDist = ClosestOpponentDistance(incarnator->location); + + static double alpha = 0.6; // 0.0-1.0 higher == more randomness + incarnator->distance = minDist * ((1.0-alpha) + 2.0*alpha*FRandom()/FIX1); + + DBG_Log("spawn", " dist= %.4f ~dist= %.4f", ToFloat(minDist), ToFloat(incarnator->distance)); + if(incarnator->distance > furthest) { + furthest = incarnator->distance; + DBG_Log("spawn", " FURTHEST SO FAR"); + } + + } else if (itsGame->spawnOrder == ksRandom) { + incarnator->distance = FRandom(); + } else { // ksUsage + incarnator->distance = FIX1; + } + + if (itsGame->spawnOrder == ksDistance || itsGame->spawnOrder == ksRandom) { + // ignore usage for these order types + incarnator->useCount = 1; } + + // to be sorted below + sortedIncarnators.push_back(incarnator); + + DBG_Log("spawn", " ~dist= %.4f, useCount=%ld", ToFloat(incarnator->distance), incarnator->useCount); + } + } + + sortedIncarnators.sort([](const CIncarnator *a, const CIncarnator *b) { + // like comparing a->distance/a->useCount to b->distance/b->useCount but avoiding divide-by-zero + return (a->distance * b->useCount) > (b->distance * a->useCount); + }); + + // try sorted Incarnators until one works + DBG_Log("spawn", "\n"); + for (auto incarnator : sortedIncarnators) { + DBG_Log("spawn", "TRYING INCARNATOR AT LOC= %s", FormatVectorFloat(incarnator->location).c_str()); + if (ReincarnateComplete(incarnator)) { + DBG_Log("spawn", "<------USING THIS INCARNATOR------>"); + return; } } + DBG_Log("spawn", "NO incarnators found, trying RANDOM"); // if couldn't find an available Incarnator above, try creating a random one - if (placeList == nullptr) { - // why 3 tries? it's somewhat arbitrary but if there's a (high) 10% chance of not working, - // then 3 tries will get that down to 0.1%. In most levels, the not-working chance is probably - // closer to 1% so 3 tries = 0.0001% - for (int tries = 3; isInLimbo && tries > 0; tries--) { - CRandomIncarnator waldo(itsGame->actorList); - if (ReincarnateComplete(&waldo)) { - break; - } + for (int tries = 3; isInLimbo && tries > 0; tries--) { + CRandomIncarnator waldo(itsGame->actorList); + if (ReincarnateComplete(&waldo)) { + break; } } } + bool CAbstractPlayer::ReincarnateComplete(CIncarnator* newSpot) { // increment useCount regardless of success, so the next player doesn't try to use this spot newSpot->useCount++; diff --git a/src/game/CAbstractPlayer.h b/src/game/CAbstractPlayer.h index a135c448..f4020d1e 100644 --- a/src/game/CAbstractPlayer.h +++ b/src/game/CAbstractPlayer.h @@ -306,6 +306,8 @@ class CAbstractPlayer : public CRealMovers { virtual void PostMortemBlast(short scoreTeam, short scoreId, Boolean doDispose); virtual void GoLimbo(FrameNumber limboDelay); + virtual void Incarnate(); + virtual Fixed ClosestOpponentDistance(Vector &location); virtual void Reincarnate(); virtual bool ReincarnateComplete(CIncarnator *newSpot); virtual void IncarnateSound(); diff --git a/src/game/CAvaraGame.cpp b/src/game/CAvaraGame.cpp index d3727624..ed3461cc 100755 --- a/src/game/CAvaraGame.cpp +++ b/src/game/CAvaraGame.cpp @@ -1137,3 +1137,14 @@ void CAvaraGame::IncrementFrame(bool firstFrame) { FrameNumber CAvaraGame::FramesFromNow(FrameNumber classicFrameCount) { return frameNumber + classicFrameCount / fpsScale; } + +void CAvaraGame::SetSpawnOrder(SpawnOrder order) { + std::string types[] = {"Random", "Usage", "Distance", "Hybrid"}; + spawnOrder = SpawnOrder(order % ksNumSpawnOrders); // guard bad inputs + std::ostringstream oss; + oss << kSpawnOrder << " = " << spawnOrder << " [" << types[spawnOrder] << "]"; + itsApp->AddMessageLine(oss.str(), MsgAlignment::Left, MsgCategory::Level); + if (gApplication) { + gApplication->Set(kSpawnOrder, spawnOrder); + } +} diff --git a/src/game/CAvaraGame.h b/src/game/CAvaraGame.h index 715f75d0..91eb6058 100644 --- a/src/game/CAvaraGame.h +++ b/src/game/CAvaraGame.h @@ -47,6 +47,14 @@ enum GameStatus { kPlayingStatus, kAbortStatus, kReadyStatus, kPauseStatus, kNoVehicleStatus, kWinStatus, kLoseStatus }; +enum SpawnOrder { + ksRandom, // picks at random which Incarnator to choose + ksUsage, // this is the "Classic" setting, uses hit count to choose + ksDistance, // uses slightly-randomized distance + ksHybrid, // uses count & distance + ksNumSpawnOrders +}; + class CAbstractActor; class CAbstractPlayer; class CPlayerManager; @@ -87,6 +95,8 @@ class CAvaraGame { FrameTime frameTime; // In milliseconds. double fpsScale; // 0.25 => CLASSICFRAMETIME / 4 + SpawnOrder spawnOrder; + GameStatus gameStatus; GameStatus statusRequest; short pausePlayer; @@ -260,6 +270,7 @@ class CAvaraGame { virtual void SetFrameTime(int32_t ft); virtual void IncrementFrame(bool firstFrame = false); virtual FrameNumber FramesFromNow(FrameNumber classicFrames); + virtual void SetSpawnOrder(SpawnOrder order); void SetKeysFromStdin() { keysFromStdin = true; }; void SetKeysToStdout() { keysToStdout = true; }; diff --git a/src/game/CIncarnator.h b/src/game/CIncarnator.h index 834b8466..b3fe2356 100644 --- a/src/game/CIncarnator.h +++ b/src/game/CIncarnator.h @@ -20,6 +20,7 @@ class CIncarnator : public CPlacedActors { MessageRecord startMsg; MessageRecord stopMsg; Boolean enabled; + Fixed distance; // randomized distance to nearest opponent virtual void BeginScript(); virtual CAbstractActor *EndScript(); diff --git a/src/game/CNetManager.cpp b/src/game/CNetManager.cpp index 40d3edb0..55b9eea5 100644 --- a/src/game/CNetManager.cpp +++ b/src/game/CNetManager.cpp @@ -685,6 +685,8 @@ void CNetManager::ResumeGame() { copy.hullType = ntohs(config.hullType); copy.frameLatency = ntohs(config.frameLatency); copy.frameTime = ntohs(config.frameTime); + copy.spawnOrder = ntohs(config.spawnOrder); + copy.hullColor = ntohl(config.hullColor.GetRaw()); copy.trimColor = ntohl(config.trimColor.GetRaw()); copy.cockpitColor = ntohl(config.cockpitColor.GetRaw()); @@ -1264,6 +1266,7 @@ void CNetManager::ConfigPlayer(short senderSlot, Ptr configData) { config->hullType = ntohs(config->hullType); config->frameLatency = ntohs(config->frameLatency); config->frameTime = ntohs(config->frameTime); + config->spawnOrder = ntohs(config->spawnOrder); config->hullColor = ntohl(config->hullColor.GetRaw()); config->trimColor = ntohl(config->trimColor.GetRaw()); config->cockpitColor = ntohl(config->cockpitColor.GetRaw()); @@ -1283,6 +1286,7 @@ void CNetManager::DoConfig(short senderSlot) { // transmitting latencyTolerance in terms of frameLatency to keep it as a short value on transmission itsGame->SetFrameTime(theConfig->frameTime); itsGame->SetFrameLatency(theConfig->frameLatency, -1); + itsGame->SetSpawnOrder((SpawnOrder)theConfig->spawnOrder); latencyVoteFrame = itsGame->NextFrameForPeriod(AUTOLATENCYPERIOD); } } @@ -1294,6 +1298,7 @@ void CNetManager::UpdateLocalConfig() { ? gApplication->Get(kLatencyToleranceTag) / itsGame->fpsScale : 0; config.frameTime = itsGame->frameTime; + config.spawnOrder = gApplication->Get(kSpawnOrder); config.hullType = gApplication ? gApplication->Number(kHullTypeTag) : 0; config.hullColor = gApplication ? ARGBColor::Parse(gApplication->String(kPlayerHullColorTag)) diff --git a/src/game/CPlayerManager.cpp b/src/game/CPlayerManager.cpp index 98c705cb..3aad7331 100644 --- a/src/game/CPlayerManager.cpp +++ b/src/game/CPlayerManager.cpp @@ -1044,7 +1044,7 @@ CAbstractPlayer *CPlayerManagerImpl::ChooseActor(CAbstractPlayer *actorList, sho itsPlayer->itsManager = shared_from_this(); itsPlayer->teamMask = myTeamMask; - itsPlayer->Reincarnate(); + itsPlayer->Incarnate(); if (itsPlayer->isInLimbo) { delete itsPlayer; @@ -1069,7 +1069,7 @@ Boolean CPlayerManagerImpl::IncarnateInAnyColor() { itsPlayer->itsManager = shared_from_this(); itsPlayer->teamMask = 1 << i; // set in case Incarnators discriminate on color - itsPlayer->Reincarnate(); + itsPlayer->Incarnate(); if (itsPlayer->isInLimbo) { delete itsPlayer; diff --git a/src/game/CScoreKeeper.cpp b/src/game/CScoreKeeper.cpp index ee6229bd..22669cfb 100644 --- a/src/game/CScoreKeeper.cpp +++ b/src/game/CScoreKeeper.cpp @@ -222,7 +222,7 @@ void CScoreKeeper::Score(ScoreInterfaceReasons reason, event.teamTarget = hitTeam; event.damage = points; - SDL_Log("CAvaraGame::Obj Collision: player:%lu, team:%lu, hit:%lu, hitTeam:%lu, weapon: %d, reason:%lu\n", iface.playerID, iface.playerTeam, hitPlayer, iface.scoreTeam, itsGame->killReason, iface.scoreReason); + DBG_Log("score", "CAvaraGame::Obj Collision: player:%lu, team:%lu, hit:%hd, hitTeam:%lu, weapon: %d, reason:%lu\n", iface.playerID, iface.playerTeam, hitPlayer, iface.scoreTeam, itsGame->killReason, iface.scoreReason); if (player >= 0 && player <= kMaxAvaraPlayers) { iface.playerName = itsGame->itsNet->playerTable[player]->PlayerName(); @@ -240,7 +240,7 @@ void CScoreKeeper::Score(ScoreInterfaceReasons reason, itsGame->itsApp->ComposeParamLine( destStr, kmAKilledBPlayer, iface.playerName, itsGame->itsNet->playerTable[hitPlayer]->PlayerName()); } - SDL_Log("CAvaraGame::Kill Event: player:%lu, team:%lu, hit:%lu, hitTeam:%lu, weapon: %d, reason:%lu\n", iface.playerID, iface.playerTeam, iface.scoreID, iface.scoreTeam, event.weaponUsed, iface.scoreReason); + DBG_Log("score", "CAvaraGame::Kill Event: player:%lu, team:%lu, hit:%lu, hitTeam:%lu, weapon: %d, reason:%lu\n", iface.playerID, iface.playerTeam, iface.scoreID, iface.scoreTeam, event.weaponUsed, iface.scoreReason); event.scoreType = ksiKillBonus; event.weaponUsed = itsGame->killReason; @@ -273,7 +273,7 @@ void CScoreKeeper::Score(ScoreInterfaceReasons reason, if(iface.scoreReason == ksiGrabBall) { event.scoreType = ksiGrabBall; itsGame->AddScoreNotify(event); - SDL_Log("CAvaraGame::Grab Ball Event: player:%lu, team:%lu, hit:%lu, hitTeam:%lu, reason:%lu\n", iface.playerID, iface.playerTeam, iface.scoreID, iface.scoreTeam, iface.scoreReason); + DBG_Log("score", "CAvaraGame::Grab Ball Event: player:%lu, team:%lu, hit:%lu, hitTeam:%lu, reason:%lu\n", iface.playerID, iface.playerTeam, iface.scoreID, iface.scoreTeam, iface.scoreReason); } if(iface.scoreReason == ksiHoldBall) { @@ -284,7 +284,7 @@ void CScoreKeeper::Score(ScoreInterfaceReasons reason, if(iface.scoreReason == ksiScoreGoal) { event.scoreType = ksiScoreGoal; itsGame->AddScoreNotify(event); - SDL_Log("CAvaraGame::Score Goal Event: player:%lu, team:%lu, hit:%lu, hitTeam:%lu, reason:%lu\n", iface.playerID, iface.playerTeam, iface.scoreID, iface.scoreTeam, iface.scoreReason); + DBG_Log("score", "CAvaraGame::Score Goal Event: player:%lu, team:%lu, hit:%lu, hitTeam:%lu, reason:%lu\n", iface.playerID, iface.playerTeam, iface.scoreID, iface.scoreTeam, iface.scoreReason); } iface.scorePoints = points; diff --git a/src/game/PlayerConfig.h b/src/game/PlayerConfig.h index ac847ad4..bf3c744b 100644 --- a/src/game/PlayerConfig.h +++ b/src/game/PlayerConfig.h @@ -19,6 +19,8 @@ struct PlayerConfigRecord { short hullType {}; short frameLatency {}; short frameTime {}; + short spawnOrder {}; + short _spare {}; ARGBColor hullColor { (*ColorManager::getMarkerColor(0)).WithA(0xff) }; ARGBColor trimColor { (*ColorManager::getMarkerColor(1)).WithA(0xff) }; ARGBColor cockpitColor { (*ColorManager::getMarkerColor(2)).WithA(0xff) }; diff --git a/src/gui/Preferences.h b/src/gui/Preferences.h index dbf546f6..c9ccb2a7 100755 --- a/src/gui/Preferences.h +++ b/src/gui/Preferences.h @@ -15,6 +15,7 @@ using json = nlohmann::json; #define kHullTypeTag "hull" #define kFrameTimeTag "frameTime" #define kThrottle "throttle" +#define kSpawnOrder "spawnOrder" // TODO: split this into separate prefs #define kServerOptionsTag "_serverOptions" @@ -171,6 +172,7 @@ static json defaultPrefs = { {kHUDShowScore, true}, {kHUDShowTime, true}, {kHUDShowKillFeed, true}, + {kSpawnOrder, 3}, {kShowNewHUD, true}, {kFrameTimeTag, 16}, {kLastAddress, ""}, diff --git a/src/util/FastMat.h b/src/util/FastMat.h index fe89efd7..2e6c953b 100644 --- a/src/util/FastMat.h +++ b/src/util/FastMat.h @@ -138,12 +138,15 @@ Fixed NormalizeVector(long n, Fixed *v); // Returns length Fixed FRandom(); Fixed FDistanceEstimate(Fixed dx, Fixed dy, Fixed dz); static inline Fixed FDistanceEstimate(Fixed* v) { return FDistanceEstimate(v[0], v[1], v[2]); }; +static inline Fixed FDistanceEstimate(Fixed *v, Fixed *w) { + return FDistanceEstimate(v[0]-w[0], v[1]-w[1], v[2]-w[2]); +}; Fixed FDistanceOverEstimate(Fixed dx, Fixed dy, Fixed dz); void InverseTransform(Matrix *trans, Matrix *inv); Fixed DistanceEstimate(Fixed x1, Fixed y1, Fixed x2, Fixed y2); -std::string FormatVector(Fixed *v, int size); -std::string FormatVectorFloat(Fixed *v, int size); +std::string FormatVector(Fixed *v, int size = 3); +std::string FormatVectorFloat(Fixed *v, int size = 3); typedef struct { float P;