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

Spawn away from enemy players #402

Merged
merged 15 commits into from
Apr 13, 2024
2 changes: 1 addition & 1 deletion levels/gzr-rip/alf/baghdad-2.alf
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,4 @@
<unique vars="mShow" />
<Base in="@start" out="mShow" cx="47.91666666666667" cz="44.375" />
<Goody shape="5560" scale="3" yon="50" sound="0" y=".001" speed="2" grenades="0" start="mShow" out="mShow" color="#ffffff" color.1="#000000" cx="47.91666666666667" cz="44.375" angle="270" />
</map>
</map>
106 changes: 85 additions & 21 deletions src/game/CAbstractPlayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<CIncarnator *> 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++;
Expand Down
2 changes: 2 additions & 0 deletions src/game/CAbstractPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
11 changes: 11 additions & 0 deletions src/game/CAvaraGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
11 changes: 11 additions & 0 deletions src/game/CAvaraGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -87,6 +95,8 @@ class CAvaraGame {
FrameTime frameTime; // In milliseconds.
double fpsScale; // 0.25 => CLASSICFRAMETIME / 4

SpawnOrder spawnOrder;

GameStatus gameStatus;
GameStatus statusRequest;
short pausePlayer;
Expand Down Expand Up @@ -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; };
Expand Down
1 change: 1 addition & 0 deletions src/game/CIncarnator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
5 changes: 5 additions & 0 deletions src/game/CNetManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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());
Expand All @@ -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);
}
}
Expand All @@ -1294,6 +1298,7 @@ void CNetManager::UpdateLocalConfig() {
? gApplication->Get<float>(kLatencyToleranceTag) / itsGame->fpsScale
: 0;
config.frameTime = itsGame->frameTime;
config.spawnOrder = gApplication->Get<short>(kSpawnOrder);
config.hullType = gApplication ? gApplication->Number(kHullTypeTag) : 0;
config.hullColor = gApplication
? ARGBColor::Parse(gApplication->String(kPlayerHullColorTag))
Expand Down
4 changes: 2 additions & 2 deletions src/game/CPlayerManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions src/game/CScoreKeeper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/game/PlayerConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) };
Expand Down
2 changes: 2 additions & 0 deletions src/gui/Preferences.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -171,6 +172,7 @@ static json defaultPrefs = {
{kHUDShowScore, true},
{kHUDShowTime, true},
{kHUDShowKillFeed, true},
{kSpawnOrder, 3},
{kShowNewHUD, true},
{kFrameTimeTag, 16},
{kLastAddress, ""},
Expand Down
7 changes: 5 additions & 2 deletions src/util/FastMat.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading