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;