From df4e99b0500474fc9882ae750f19b85e887a3869 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Tue, 23 Jul 2024 20:48:53 -0400 Subject: [PATCH 01/38] WIP Race Checkpoints MobjList count WIP: Checkpoints grant lap bonus help? can't allocate vector fixed tagged line iteration and collision detection Multiplayer animations and map retart fixes Clear between maps --- src/d_clisrv.c | 16 ++ src/d_clisrv.h | 1 + src/g_game.c | 2 +- src/k_objects.h | 3 + src/k_rank.cpp | 4 +- src/k_tally.cpp | 3 +- src/mobj_list.hpp | 6 + src/objects/checkpoint.cpp | 317 +++++++++++++++++++++++++++---------- src/p_mobj.c | 2 - src/p_setup.cpp | 3 + src/p_spec.c | 6 + 11 files changed, 272 insertions(+), 91 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index c3c54ea99..c4adc7476 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -6805,6 +6805,22 @@ INT32 D_NumPlayers(void) return num; } +/** Returns the number of players racing, not spectating and includes bots + * \return Number of players. Can be zero if we're running a ::dedicated + * server. + */ +INT32 D_NumPlayersInRace(void) +{ + INT32 numPlayers = 0; + INT32 i; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator) + numPlayers++; + } + return numPlayers; +} + /** Return whether a player is a real person (not a CPU) and not spectating. */ boolean D_IsPlayerHumanAndGaming (INT32 player_number) diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 64a75744e..3b6889fcb 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -657,6 +657,7 @@ extern UINT8 playernode[MAXPLAYERS]; extern UINT8 playerconsole[MAXPLAYERS]; INT32 D_NumPlayers(void); +INT32 D_NumPlayersInRace(void); boolean D_IsPlayerHumanAndGaming(INT32 player_number); void D_ResetTiccmds(void); diff --git a/src/g_game.c b/src/g_game.c index 1628e6d36..e5f711253 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -5248,7 +5248,7 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr players[i].score = 0; } - if (resetplayer || map != gamemap) + if (resetplayer || !(gametyperules & GTR_CHECKPOINTS && map == gamemap)) { players[i].checkpointId = 0; } diff --git a/src/k_objects.h b/src/k_objects.h index 9a2d314bc..d0fd010fc 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -263,6 +263,9 @@ mobj_t *Obj_FindCheckpoint(INT32 id); boolean Obj_GetCheckpointRespawnPosition(const mobj_t *checkpoint, vector3_t *return_pos); angle_t Obj_GetCheckpointRespawnAngle(const mobj_t *checkpoint); void Obj_ActivateCheckpointInstantly(mobj_t* mobj); +UINT32 Obj_GetCheckpointCount(); +void Obj_ClearCheckpoints(); +void Obj_DeactivateCheckpoints(); /* Rideroid / Rideroid Node */ void Obj_RideroidThink(mobj_t *mo); diff --git a/src/k_rank.cpp b/src/k_rank.cpp index af7f12bf1..aa6322f5c 100644 --- a/src/k_rank.cpp +++ b/src/k_rank.cpp @@ -26,6 +26,7 @@ #include "byteptr.h" #include "k_race.h" #include "command.h" +#include "k_objects.h" // I was ALMOST tempted to start tearing apart all // of the map loading code and turning it into C++ @@ -510,7 +511,8 @@ void gpRank_t::Update(void) } lvl->time = UINT32_MAX; - lvl->totalLapPoints = K_RaceLapCount(gamemap - 1) * 2; + + lvl->totalLapPoints = ( K_RaceLapCount(gamemap - 1) + Obj_GetCheckpointCount() )* 2; lvl->totalPrisons = maptargets; UINT8 i; diff --git a/src/k_tally.cpp b/src/k_tally.cpp index ee9b0373d..d2783accb 100644 --- a/src/k_tally.cpp +++ b/src/k_tally.cpp @@ -36,6 +36,7 @@ #include "r_fps.h" #include "g_party.h" #include "g_input.h" +#include "k_objects.h" boolean level_tally_t::UseBonuses(void) { @@ -343,7 +344,7 @@ void level_tally_t::Init(player_t *player) if ((gametypes[gt]->rules & GTR_CIRCUIT) == GTR_CIRCUIT) { laps = player->lapPoints; - totalLaps = numlaps; + totalLaps = numlaps + numlaps * Obj_GetCheckpointCount(); if (inDuel == false) { diff --git a/src/mobj_list.hpp b/src/mobj_list.hpp index ec277adf5..bf46e7e38 100644 --- a/src/mobj_list.hpp +++ b/src/mobj_list.hpp @@ -37,6 +37,7 @@ struct MobjList { ptr->next(front()); front(ptr); + count_++; } void erase(T* node) @@ -45,6 +46,7 @@ struct MobjList { front(node->next()); node->next(nullptr); + count_--; return; } @@ -69,6 +71,7 @@ struct MobjList { prev->next(node->next()); node->next(nullptr); + count_--; break; } } @@ -77,9 +80,12 @@ struct MobjList auto begin() const { return view().begin(); } auto end() const { return view().end(); } + auto count() { return count_; } + private: void front(T* ptr) { Mobj::ManagedPtr {Head} = ptr; } auto view() const { return MobjListView(front(), [](T* node) { return node->next(); }); } + UINT32 count_ = 0; }; }; // namespace srb2 diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index 7269e5b79..c1170d3c9 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -17,6 +17,7 @@ #include "../doomdef.h" #include "../doomtype.h" #include "../info.h" +#include "../g_game.h" #include "../k_color.h" #include "../k_kart.h" #include "../k_objects.h" @@ -34,9 +35,17 @@ #include "../sounds.h" #include "../tables.h" +using std::vector; +using std::pair; +using std::min; +using std::max; +using std::clamp; + extern mobj_t* svg_checkpoints; #define checkpoint_id(o) ((o)->thing_args[0]) +#define checkpoint_linetag(o) ((o)->thing_args[1]) +#define checkpoint_extralength(o) ((o)->thing_args[2]) #define checkpoint_other(o) ((o)->target) #define checkpoint_orb(o) ((o)->tracer) #define checkpoint_arm(o) ((o)->hnext) @@ -51,12 +60,14 @@ namespace struct LineOnDemand : line_t { + LineOnDemand(const line_t* line) {} + LineOnDemand(fixed_t x1, fixed_t y1, fixed_t x2, fixed_t y2) : line_t { .v1 = &v1_data_, .dx = x2 - x1, .dy = y2 - y1, - .bbox = {std::max(y1, y2), std::min(y1, y2), std::min(x1, x2), std::max(x1, x2)}, + .bbox = {max(y1, y2), min(y1, y2), min(x1, x2), max(x1, x2)}, }, v1_data_ {.x = x1, .y = y1} { @@ -76,6 +87,12 @@ struct LineOnDemand : line_t bbox[BOXLEFT] <= other.bbox[BOXRIGHT] && bbox[BOXRIGHT] >= other.bbox[BOXLEFT]; } + bool overlaps(const line_t& other) const + { + return bbox[BOXTOP] >= other.bbox[BOXBOTTOM] && bbox[BOXBOTTOM] <= other.bbox[BOXTOP] && + bbox[BOXLEFT] <= other.bbox[BOXRIGHT] && bbox[BOXRIGHT] >= other.bbox[BOXLEFT]; + } + private: vertex_t v1_data_; }; @@ -170,6 +187,30 @@ struct Checkpoint : mobj_t deactivate(); } + // will not work properly after a player enters intoa new lap + INT32 players_passed() + { + INT32 pcount = 0; + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && !players[i].spectator && players[i].checkpointId >= id()) + pcount++; + } + return pcount; + } + + boolean top_half_has_passed() + { + INT32 pcount = 0; + INT32 winningpos = 1; + + INT32 nump = D_NumPlayersInRace(); + winningpos = nump / 2; + winningpos += nump % 2; + + return players_passed() >= winningpos; + } + void animate() { orient(); @@ -181,10 +222,11 @@ struct Checkpoint : mobj_t if (!clip_var()) { - speed(speed() - FixedDiv(speed() / 50, std::max(speed_multiplier(), 1))); + speed(speed() - FixedDiv(speed() / 50, max(speed_multiplier(), 1))); } } - else if (!activated()) + + if (!top_half_has_passed()) { sparkle_between(0); } @@ -193,7 +235,7 @@ struct Checkpoint : mobj_t void twirl(angle_t dir, fixed_t multiplier) { var(0); - speed_multiplier(std::clamp(multiplier, kMinSpeedMultiplier, kMaxSpeedMultiplier)); + speed_multiplier(clamp(multiplier, kMinSpeedMultiplier, kMaxSpeedMultiplier)); speed(FixedDiv(kBaseSpeed, speed_multiplier())); reverse(AngleDeltaSigned(angle_to_other(), dir) > 0); @@ -266,7 +308,7 @@ private: kMinPivotDelay ); - return to_angle(FixedDiv(std::max(var(), pos) - pos, FRACUNIT - pos)) / 4; + return to_angle(FixedDiv(max(var(), pos) - pos, FRACUNIT - pos)) / 4; } void orient() @@ -304,7 +346,7 @@ private: } } - void spawn_sparkle(const vector3_t& pos, fixed_t xy_momentum, fixed_t z_momentum, angle_t dir) + void spawn_sparkle(const vector3_t& pos, fixed_t xy_momentum, fixed_t z_momentum, angle_t dir, skincolornum_t color = SKINCOLOR_ULTRAMARINE) { auto rng = [=](int units) { return P_RandomRange(PR_DECORATION, -(units) * scale, +(units) * scale); }; @@ -324,10 +366,10 @@ private: if (xy_momentum) { P_Thrust(p, dir, xy_momentum); - p->momz = P_RandomKey(PR_DECORATION, std::max(z_momentum, 1)); + p->momz = P_RandomKey(PR_DECORATION, max(z_momentum, 1)); p->destscale = 0; p->scalespeed = p->scale / 35; - p->color = SKINCOLOR_ULTRAMARINE; + p->color = color; p->fuse = 0; // Something lags at the start of the level. The @@ -342,7 +384,7 @@ private: } else { - p->color = K_RainbowColor(leveltime); + p->color = color; p->fuse = 2; } } @@ -369,7 +411,8 @@ private: {x + FixedMul(ofs, FCOS(a)), y + FixedMul(ofs, FSIN(a)), z + (kSparkleZ * scale)}, momentum, momentum / 2, - dir + dir, + activated() ? SKINCOLOR_GREEN : SKINCOLOR_ULTRAMARINE ); } } @@ -402,14 +445,93 @@ struct CheckpointManager auto begin() { return list_.begin(); } auto end() { return list_.end(); } - auto find(INT32 id) { return std::find_if(begin(), end(), [id](Checkpoint* chk) { return chk->id() == id; }); } + auto find_checkpoint(INT32 id) { + auto it = find_if(list_.begin(), list_.end(), [id](auto pair) { return pair.first->id() == id; }); + if (it != list_.end()) + { + return it->first; + } + return static_cast(nullptr); + } - void push_front(Checkpoint* chk) { list_.push_front(chk); } + // auto find_pair(Checkpoint* chk) { + // pair> retpair; + // auto it = find_if(list_.begin(), list_.end(), [chk](auto pair) { return pair.first == chk; }); + // if (it != list_.end()) + // { + // retpair = *it; + // return retpair; + // } + // return static_cast>>(nullptr); + // } - void erase(Checkpoint* chk) { list_.erase(chk); } + void remove_checkpoint(mobj_t* end) + { + auto chk = static_cast(end); + auto it = find_if(list_.begin(), list_.end(), [&](auto pair) { return pair.first == chk; }); + if (it != list_.end()) + { + list_.erase(it); + } + } + + void link_checkpoint(mobj_t* end) + { + auto chk = static_cast(end); + auto id = chk->id(); + if (chk->spawnpoint && id == 0) + { + auto msg = fmt::format( + "Checkpoint thing (index #{}, thing type {}) has an invalid ID! ID must not be 0.\n", + chk->spawnpoint - mapthings, + chk->spawnpoint->type + ); + CONS_Alert(CONS_WARNING, "%s", msg.c_str()); + return; + } + + if (auto other = find_checkpoint(id)) + { + if (chk->spawnpoint && other->spawnpoint && chk->spawnpoint->angle != other->spawnpoint->angle) + { + auto msg = fmt::format( + "Checkpoints things with ID {} (index #{} and #{}, thing type {}) do not have matching angles.\n", + chk->id(), + chk->spawnpoint - mapthings, + other->spawnpoint - mapthings, + chk->spawnpoint->type + ); + CONS_Alert(CONS_WARNING, "%s", msg.c_str()); + return; + } + other->other(chk); + chk->other(other); + } + else // Checkpoint isn't in the list, find any associated tagged lines and make the pair + { + vector checklines; + if (checkpoint_linetag(chk)) + { + INT32 li; + INT32 tag = checkpoint_linetag(chk); + TAG_ITER_LINES(tag, li) + { + line_t* line = lines + li; + checklines.push_back(line); + } + } + list_.emplace_back(chk, move(checklines)); + } + + chk->gingerbread(); + } + + void clear() { list_.clear(); } + + auto count() { return list_.size(); } private: - srb2::MobjList list_; + vector>> list_; }; CheckpointManager g_checkpoints; @@ -418,54 +540,15 @@ CheckpointManager g_checkpoints; void Obj_LinkCheckpoint(mobj_t* end) { - auto chk = static_cast(end); - - if (chk->spawnpoint && chk->id() == 0) - { - auto msg = fmt::format( - "Checkpoint thing (index #{}, thing type {}) has an invalid ID! ID must not be 0.\n", - chk->spawnpoint - mapthings, - chk->spawnpoint->type - ); - CONS_Alert(CONS_WARNING, "%s", msg.c_str()); - return; - } - - if (auto it = g_checkpoints.find(chk->id()); it != g_checkpoints.end()) - { - Checkpoint* other = *it; - - if (chk->spawnpoint && other->spawnpoint && chk->spawnpoint->angle != other->spawnpoint->angle) - { - auto msg = fmt::format( - "Checkpoints things with ID {} (index #{} and #{}, thing type {}) do not have matching angles.\n", - chk->id(), - chk->spawnpoint - mapthings, - other->spawnpoint - mapthings, - chk->spawnpoint->type - ); - CONS_Alert(CONS_WARNING, "%s", msg.c_str()); - return; - } - - other->other(chk); - chk->other(other); - } - else - { - g_checkpoints.push_front(chk); - } - - chk->gingerbread(); + g_checkpoints.link_checkpoint(end); } void Obj_UnlinkCheckpoint(mobj_t* end) { auto chk = static_cast(end); - - g_checkpoints.erase(chk); - + g_checkpoints.remove_checkpoint(end); P_RemoveMobj(chk->orb()); + P_RemoveMobj(chk->arm()); } void Obj_CheckpointThink(mobj_t* end) @@ -480,39 +563,64 @@ void Obj_CheckpointThink(mobj_t* end) chk->animate(); } -void Obj_CrossCheckpoints(player_t* player, fixed_t old_x, fixed_t old_y) +void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixed_t old_x, fixed_t old_y) { LineOnDemand ray(old_x, old_y, player->mo->x, player->mo->y, player->mo->radius); - auto it = std::find_if( + auto it = find_if( g_checkpoints.begin(), g_checkpoints.end(), - [&](const Checkpoint* chk) + [&](auto chkpair) { + Checkpoint* chk = chkpair.first; if (!chk->valid()) { return false; } - LineOnDemand gate = chk->crossing_line(); + LineOnDemand* gate; + + if (chkpair.second.empty()) + { + LineOnDemand dyngate = chk->crossing_line(); + if (!ray.overlaps(dyngate)) + return false; + gate = &dyngate; + } + else + { + auto it = find_if( + chkpair.second.begin(), + chkpair.second.end(), + [&](const line_t* line) + { + return ray.overlaps(*line); + } + ); + + if (it == chkpair.second.end()) + { + return false; + } + + line_t* line = *it; + gate = static_cast(line); + } // Check if the bounding boxes of the two lines // overlap. This relies on the player movement not // being so large that it creates an oversized box, // but thankfully that doesn't seem to happen, under // normal circumstances. - if (!ray.overlaps(gate)) - { - return false; - } - INT32 side = P_PointOnLineSide(player->mo->x, player->mo->y, &gate); - INT32 oldside = P_PointOnLineSide(old_x, old_y, &gate); + INT32 side = P_PointOnLineSide(player->mo->x, player->mo->y, gate); + INT32 oldside = P_PointOnLineSide(old_x, old_y, gate); if (side == oldside) { // Did not cross. return false; + } return true; @@ -524,41 +632,54 @@ void Obj_CrossCheckpoints(player_t* player, fixed_t old_x, fixed_t old_y) return; } - Checkpoint* chk = *it; + Checkpoint* chk = it->first; - if (chk->activated()) + if (player->checkpointId == chk->id()) { return; } - for (Checkpoint* chk : g_checkpoints) + if (player->position <= 1) { - if (chk->valid()) - { - // Swing down any previously passed checkpoints. - // TODO: this could look weird in multiplayer if - // other players cross different checkpoints. - chk->untwirl(); - chk->other()->untwirl(); - } + angle_t direction = R_PointToAngle2(old_x, old_y, player->mo->x, player->mo->y); + fixed_t speed_multiplier = FixedDiv(player->speed, K_GetKartSpeed(player, false, false)); + chk->twirl(direction, speed_multiplier); + chk->other()->twirl(direction, speed_multiplier); } - angle_t direction = R_PointToAngle2(old_x, old_y, player->mo->x, player->mo->y); - fixed_t speed_multiplier = FixedDiv(player->speed, K_GetKartSpeed(player, false, false)); - - chk->twirl(direction, speed_multiplier); - chk->other()->twirl(direction, speed_multiplier); + if (gametyperules & GTR_CHECKPOINTS) + { + for (auto chkpair : g_checkpoints) + { + Checkpoint* chk = chkpair.first; + if (chk->valid()) + { + chk->untwirl(); + chk->other()->untwirl(); + } + } + } S_StartSound(player->mo, sfx_s3k63); player->checkpointId = chk->id(); + + if (D_NumPlayersInRace() > 1 && !K_IsPlayerLosing(player)) + { + if (player->position == 1) + { + player->lapPoints += 2; + } + else + { + player->lapPoints += 1; + } + } } -mobj_t *Obj_FindCheckpoint(INT32 id) +mobj_t* Obj_FindCheckpoint(INT32 id) { - auto it = g_checkpoints.find(id); - - return it != g_checkpoints.end() ? *it : nullptr; + return g_checkpoints.find_checkpoint(id); } boolean Obj_GetCheckpointRespawnPosition(const mobj_t* mobj, vector3_t* return_pos) @@ -593,3 +714,27 @@ void Obj_ActivateCheckpointInstantly(mobj_t* mobj) chk->other()->activate(); } } + +// Returns a count of checkpoint gates, not objects +UINT32 Obj_GetCheckpointCount() +{ + return g_checkpoints.count(); +} + +void Obj_ClearCheckpoints() +{ + g_checkpoints.clear(); +} + +void Obj_DeactivateCheckpoints() +{ + for (auto chkpair : g_checkpoints) + { + Checkpoint* chk = chkpair.first; + if (chk->valid()) + { + chk->untwirl(); + chk->other()->untwirl(); + } + } +} \ No newline at end of file diff --git a/src/p_mobj.c b/src/p_mobj.c index d6d2cff6e..412984ae2 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -12561,8 +12561,6 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) return false; break; case MT_CHECKPOINT_END: - if (!(gametyperules & GTR_CHECKPOINTS)) - return false; break; default: break; diff --git a/src/p_setup.cpp b/src/p_setup.cpp index 0b6422ee1..e719c755a 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -118,6 +118,7 @@ #include "k_hud.h" // K_ClearPersistentMessages #include "k_endcam.h" #include "k_credits.h" +#include "k_objects.h" // Replay names have time #if !defined (UNDER_CE) @@ -8586,6 +8587,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) LUA_InvalidateLevel(); + Obj_ClearCheckpoints(); + for (ss = sectors; sectors+numsectors != ss; ss++) { Z_Free(ss->attached); diff --git a/src/p_spec.c b/src/p_spec.c index db21a7cb1..50caec45a 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2117,6 +2117,12 @@ static void K_HandleLapIncrement(player_t *player) player->lapPoints++; } } + + if (player->position == 1 && !(gametyperules & GTR_CHECKPOINTS)) + { + Obj_DeactivateCheckpoints(); + } + player->checkpointId = 0; } // Set up lap animation vars From 89544772b39140e15c6493d01c97077f429dd7c3 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 13 Aug 2024 17:54:31 -0700 Subject: [PATCH 02/38] Amps for checkpoints (and small amp fixes) --- src/k_kart.c | 4 ++-- src/objects/amps.c | 2 +- src/objects/checkpoint.cpp | 3 +++ src/p_spec.c | 3 +++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 48e06e54d..aa8b7b792 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4091,7 +4091,7 @@ boolean K_Overdrive(player_t *player) S_StartSound(player->mo, sfx_cdfm35); S_StartSound(player->mo, sfx_cdfm13); - player->overdrive += (player->amps)*6; + player->overdrive += (player->amps)*3; player->overshield += (player->amps)*2; player->overdrivepower = FRACUNIT; @@ -4112,7 +4112,7 @@ boolean K_DefensiveOverdrive(player_t *player) S_StartSound(player->mo, sfx_cdfm35); S_StartSound(player->mo, sfx_cdfm13); - player->overdrive += (player->amps)*4; + player->overdrive += (player->amps)*2; player->overshield += (player->amps)*2 + TICRATE*2; player->overdrivepower = FRACUNIT; diff --git a/src/objects/amps.c b/src/objects/amps.c index dab90aba2..89d86344f 100644 --- a/src/objects/amps.c +++ b/src/objects/amps.c @@ -55,7 +55,7 @@ void Obj_AmpsThink (mobj_t *amps) amps->extravalue2++; - speed += amps->extravalue1 * amps->scale/2; + speed += amps->extravalue2 * amps->scale/2; fakez = mo->z + (vert * amps->extravalue1 / AMP_ARCTIME); damper = 1; diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index c1170d3c9..3f6753fc1 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -674,6 +674,9 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe { player->lapPoints += 1; } + + K_SpawnAmps(player, 5*(D_NumPlayersInRace() - player->position), static_cast(chk)); + K_SpawnAmps(player, 5*(D_NumPlayersInRace() - player->position), static_cast(chk->other())); } } diff --git a/src/p_spec.c b/src/p_spec.c index 50caec45a..ef1427938 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2116,6 +2116,9 @@ static void K_HandleLapIncrement(player_t *player) { player->lapPoints++; } + + if (player->laps <= numlaps) + K_SpawnAmps(player, 10*(D_NumPlayersInRace() - player->position), player->mo); } if (player->position == 1 && !(gametyperules & GTR_CHECKPOINTS)) From f9b72269f76b44354fc29c628b60f5d509fc8a46 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 13 Aug 2024 18:14:45 -0700 Subject: [PATCH 03/38] Further checkpoint amp nerfs --- src/objects/checkpoint.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index 3f6753fc1..69f0b3af2 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -675,8 +675,8 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe player->lapPoints += 1; } - K_SpawnAmps(player, 5*(D_NumPlayersInRace() - player->position), static_cast(chk)); - K_SpawnAmps(player, 5*(D_NumPlayersInRace() - player->position), static_cast(chk->other())); + K_SpawnAmps(player, 100*(D_NumPlayersInRace() - player->position)/50, static_cast(chk)); + K_SpawnAmps(player, 100*(D_NumPlayersInRace() - player->position)/50, static_cast(chk->other())); } } From cd625acab7af5552c400fedec6d871b2093f60f8 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 18 Aug 2024 19:05:33 -0400 Subject: [PATCH 04/38] Remove amps from checkpoints --- src/objects/checkpoint.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index 69f0b3af2..c1170d3c9 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -674,9 +674,6 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe { player->lapPoints += 1; } - - K_SpawnAmps(player, 100*(D_NumPlayersInRace() - player->position)/50, static_cast(chk)); - K_SpawnAmps(player, 100*(D_NumPlayersInRace() - player->position)/50, static_cast(chk->other())); } } From b4827de43d54174e310e7ec1be86f6973dcfc163 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 18 Aug 2024 20:54:16 -0400 Subject: [PATCH 05/38] WIP basic exp modifies item odds based on checkpoint and finishlin grading --- src/d_player.h | 1 + src/g_game.c | 4 ++++ src/k_hud.cpp | 3 +++ src/k_roulette.c | 10 +++++++--- src/objects/checkpoint.cpp | 6 ++++++ src/p_saveg.c | 2 ++ src/p_spec.c | 6 ++++++ 7 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index b2a513339..68b34a963 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -914,6 +914,7 @@ struct player_t UINT8 laps; // Number of laps (optional) UINT8 latestlap; UINT32 lapPoints; // Points given from laps + INT32 exp; INT32 cheatchecknum; // The number of the last cheatcheck you hit INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp diff --git a/src/g_game.c b/src/g_game.c index e5f711253..4ba14b585 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2131,6 +2131,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT8 laps; UINT8 latestlap; UINT32 lapPoints; + INT32 exp; UINT16 skincolor; INT32 skin; UINT8 availabilities[MAXAVAILABILITY]; @@ -2319,6 +2320,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) laps = 0; latestlap = 0; lapPoints = 0; + exp = FRACUNIT; roundscore = 0; exiting = 0; khudfinish = 0; @@ -2356,6 +2358,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) laps = players[player].laps; latestlap = players[player].latestlap; lapPoints = players[player].lapPoints; + exp = players[player].exp; roundscore = players[player].roundscore; @@ -2470,6 +2473,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->laps = laps; p->latestlap = latestlap; p->lapPoints = lapPoints; + p->exp = exp; p->totalring = totalring; for (i = 0; i < LAP__MAX; i++) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 8576d578c..c3e6576f0 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -6512,6 +6512,9 @@ void K_drawKartHUD(void) if (cv_kartdebugdistribution.value) K_drawDistributionDebugger(); + // temp debug + V_DrawSmallString(8, 2, V_SNAPTOTOP, va("Exp/Dist mult: %.2f", FixedToFloat(stplyr->exp))); + if (cv_kartdebugnodes.value) { UINT8 p; diff --git a/src/k_roulette.c b/src/k_roulette.c index 1a44f0b69..4bda7e7dd 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -423,13 +423,14 @@ static UINT32 K_UndoMapScaling(UINT32 distance) as well as Frantic Items. Input Arguments:- + player - The player to get the distance of. distance - Original distance. numPlayers - Number of players in the game. Return:- New distance after scaling. --------------------------------------------------*/ -static UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) +static UINT32 K_ScaleItemDistance(const player_t* player, UINT32 distance, UINT8 numPlayers) { if (franticitems == true) { @@ -443,6 +444,9 @@ static UINT32 K_ScaleItemDistance(UINT32 distance, UINT8 numPlayers) FRACUNIT + (K_ItemOddsScale(numPlayers) / 2) ); + // Distance is reduced based on the player's exp + distance = FixedMul(distance, min(FRACUNIT, player->exp)); + return distance; } @@ -509,7 +513,7 @@ UINT32 K_GetItemRouletteDistance(const player_t *player, UINT8 numPlayers) } pdis = K_UndoMapScaling(pdis); - pdis = K_ScaleItemDistance(pdis, numPlayers); + pdis = K_ScaleItemDistance(player, pdis, numPlayers); if (player->bot && (player->botvars.rival || cv_levelskull.value)) { @@ -1150,7 +1154,7 @@ static void K_InitRoulette(itemroulette_t *const roulette) && roulette->secondDist > roulette->firstDist) { roulette->secondToFirst = roulette->secondDist - roulette->firstDist; - roulette->secondToFirst = K_ScaleItemDistance(roulette->secondToFirst, 16 - roulette->playing); // Reversed scaling + roulette->secondToFirst = K_ScaleItemDistance(&players[i], roulette->secondToFirst, 16 - roulette->playing); // Reversed scaling } } diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index c1170d3c9..8850db58a 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -669,12 +669,18 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe if (player->position == 1) { player->lapPoints += 2; + player->exp += FRACUNIT/10; } else { player->lapPoints += 1; + player->exp += FRACUNIT/20; } } + else if (K_IsPlayerLosing(player)) + { + player->exp -= FRACUNIT/20; + } } mobj_t* Obj_FindCheckpoint(INT32 id) diff --git a/src/p_saveg.c b/src/p_saveg.c index 8ca5c3d09..a9e811961 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -281,6 +281,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].laps); WRITEUINT8(save->p, players[i].latestlap); WRITEUINT32(save->p, players[i].lapPoints); + WRITEINT32(save->p, players[i].exp); WRITEINT32(save->p, players[i].cheatchecknum); WRITEINT32(save->p, players[i].checkpointId); @@ -937,6 +938,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].laps = READUINT8(save->p); // Number of laps (optional) players[i].latestlap = READUINT8(save->p); players[i].lapPoints = READUINT32(save->p); + players[i].exp = READINT32(save->p); players[i].cheatchecknum = READINT32(save->p); players[i].checkpointId = READINT32(save->p); diff --git a/src/p_spec.c b/src/p_spec.c index ef1427938..281ff57c9 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2111,15 +2111,21 @@ static void K_HandleLapIncrement(player_t *player) if (inDuel == false && player->position == 1) // 1st place in 1v1 uses thumbs up { player->lapPoints += 2; + player->exp += FRACUNIT/10; } else { player->lapPoints++; + player->exp += FRACUNIT/20; } if (player->laps <= numlaps) K_SpawnAmps(player, 10*(D_NumPlayersInRace() - player->position), player->mo); } + else if (K_IsPlayerLosing(player)) + { + player->exp -= FRACUNIT/20; + } if (player->position == 1 && !(gametyperules & GTR_CHECKPOINTS)) { From 6f455bc1fbccd1afc26d2ab64661128f9d916b69 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 18 Aug 2024 21:05:01 -0400 Subject: [PATCH 06/38] Remove amps from finish line cross --- src/p_spec.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/p_spec.c b/src/p_spec.c index 281ff57c9..efa036008 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2118,9 +2118,6 @@ static void K_HandleLapIncrement(player_t *player) player->lapPoints++; player->exp += FRACUNIT/20; } - - if (player->laps <= numlaps) - K_SpawnAmps(player, 10*(D_NumPlayersInRace() - player->position), player->mo); } else if (K_IsPlayerLosing(player)) { From 57fb5b758caa29dda363287f1393fab5afa9861d Mon Sep 17 00:00:00 2001 From: Ashnal Date: Sun, 18 Aug 2024 21:05:13 -0400 Subject: [PATCH 07/38] Update pwr levels on checkpoint cross --- src/objects/checkpoint.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index 8850db58a..94851cc6f 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -681,6 +681,8 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe { player->exp -= FRACUNIT/20; } + + K_UpdatePowerLevels(player, player->laps, false); } mobj_t* Obj_FindCheckpoint(INT32 id) From 9cfa67dae6bb78bef565c7c08abb58bfdd9ad824 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Mon, 19 Aug 2024 00:10:45 -0400 Subject: [PATCH 08/38] WIP Exp math temp changes to distribution debugger --- src/d_player.h | 2 +- src/k_hud.cpp | 78 +++++++++++++++++++++----------------- src/k_kart.c | 21 ++++++++++ src/k_kart.h | 2 + src/k_roulette.c | 7 +++- src/objects/checkpoint.cpp | 8 +--- src/p_spec.c | 8 +--- 7 files changed, 76 insertions(+), 50 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 68b34a963..565192267 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -497,7 +497,7 @@ struct itemroulette_t UINT8 useOdds; UINT8 playing, exiting; - UINT32 dist, baseDist; + UINT32 preexpdist, preexpuseOdds, dist, baseDist; UINT32 firstDist, secondDist; UINT32 secondToFirst; diff --git a/src/k_hud.cpp b/src/k_hud.cpp index c3e6576f0..55920f755 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -5698,46 +5698,53 @@ static void K_drawDistributionDebugger(void) K_FillItemRouletteData(stplyr, &rouletteData, false); - for (i = 0; i < rouletteData.itemListLen; i++) - { - const kartitems_t item = static_cast(rouletteData.itemList[i]); - UINT8 amount = 1; + // for (i = 0; i < rouletteData.itemListLen; i++) + // { + // const kartitems_t item = static_cast(rouletteData.itemList[i]); + // UINT8 amount = 1; - if (y > (BASEVIDHEIGHT << FRACBITS) - space - pad) - { - x += space; - y = -pad; - } + // if (y > (BASEVIDHEIGHT << FRACBITS) - space - pad) + // { + // x += space; + // y = -pad; + // } - V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, - K_GetSmallStaticCachedItemPatch(item), NULL); + // V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, + // K_GetSmallStaticCachedItemPatch(item), NULL); - // Display amount for multi-items - amount = K_ItemResultToAmount(item); - if (amount > 1) - { - V_DrawStringScaled( - x + (18 * scale), - y + (23 * scale), - scale, FRACUNIT, FRACUNIT, - V_SNAPTOTOP, - NULL, HU_FONT, - va("x%d", amount) - ); - } + // // Display amount for multi-items + // amount = K_ItemResultToAmount(item); + // if (amount > 1) + // { + // V_DrawStringScaled( + // x + (18 * scale), + // y + (23 * scale), + // scale, FRACUNIT, FRACUNIT, + // V_SNAPTOTOP, + // NULL, HU_FONT, + // va("x%d", amount) + // ); + // } - y += space; - } + // y += space; + // } - V_DrawString((x >> FRACBITS) + 20, 2, V_SNAPTOTOP, va("useOdds[%u]", rouletteData.useOdds)); - V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); + // V_DrawString((x >> FRACBITS) + 20, 2, V_SNAPTOTOP, va("useOdds[%u]", rouletteData.useOdds)); + // V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); + + // V_DrawString((x >> FRACBITS) + 20, 22, V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist)); + // V_DrawString((x >> FRACBITS) + 20, 30, V_SNAPTOTOP, va("dist = %u", rouletteData.dist)); + + // V_DrawString((x >> FRACBITS) + 20, 42, V_SNAPTOTOP, va("firstDist = %u", rouletteData.firstDist)); + // V_DrawString((x >> FRACBITS) + 20, 50, V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); + // V_DrawString((x >> FRACBITS) + 20, 58, V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); + + V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("pre-exp dist %u", rouletteData.preexpdist)); + V_DrawString((x >> FRACBITS) + 20, 18, V_SNAPTOTOP, va("pre-exp useOdds %u", rouletteData.preexpuseOdds)); + V_DrawString((x >> FRACBITS) + 20, 26, V_SNAPTOTOP, va(" dist %u", rouletteData.dist)); + V_DrawString((x >> FRACBITS) + 20, 34, V_SNAPTOTOP, va(" useOdds %u", rouletteData.useOdds)); - V_DrawString((x >> FRACBITS) + 20, 22, V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist)); - V_DrawString((x >> FRACBITS) + 20, 30, V_SNAPTOTOP, va("dist = %u", rouletteData.dist)); - V_DrawString((x >> FRACBITS) + 20, 42, V_SNAPTOTOP, va("firstDist = %u", rouletteData.firstDist)); - V_DrawString((x >> FRACBITS) + 20, 50, V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); - V_DrawString((x >> FRACBITS) + 20, 58, V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); #ifndef ITEM_LIST_SIZE Z_Free(rouletteData.itemList); @@ -6509,11 +6516,12 @@ void K_drawKartHUD(void) K_drawInput(); } - if (cv_kartdebugdistribution.value) + // if (cv_kartdebugdistribution.value) K_drawDistributionDebugger(); // temp debug - V_DrawSmallString(8, 2, V_SNAPTOTOP, va("Exp/Dist mult: %.2f", FixedToFloat(stplyr->exp))); + V_DrawString(8, 2, V_SNAPTOTOP, va("Exp/Dist mult: %.2f", FixedToFloat(stplyr->exp))); + // V_DrawSmallString(8, 4, V_SNAPTOTOP, va("Exp/Dist mult: %.2f", FixedToFloat(stplyr->exp))); if (cv_kartdebugnodes.value) { diff --git a/src/k_kart.c b/src/k_kart.c index aa8b7b792..a35682bdd 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12703,6 +12703,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) else { UINT32 behind = K_GetItemRouletteDistance(player, player->itemRoulette.playing); + behind = FixedMul(behind, K_GetExpAdjustment(player)); UINT32 behindMulti = behind / 500; behindMulti = min(behindMulti, 60); award = award * (behindMulti + 10) / 10; @@ -14768,4 +14769,24 @@ boolean K_PlayerCanUseItem(player_t *player) return (player->mo->health > 0 && !player->spectator && !P_PlayerInPain(player) && !mapreset && leveltime > introtime); } +fixed_t K_GetExpAdjustment(player_t *player) +{ + fixed_t exp_power = 1*FRACUNIT/100; // adjust to change overall xp volatility + fixed_t exp_drainrate = 995*FRACUNIT/1000; // adjust to change overall item chaos + fixed_t result = 0; + + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || player->spectator) + continue; + + result -= exp_power; + if (player->position < players[i].position) + { + result += FixedMul(exp_power, exp_drainrate); + } + } + return result; +} + //} diff --git a/src/k_kart.h b/src/k_kart.h index 298296d11..0f6c14c5f 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -288,6 +288,8 @@ boolean K_ThunderDome(void); boolean K_PlayerCanUseItem(player_t *player); +fixed_t K_GetExpAdjustment(player_t *player); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_roulette.c b/src/k_roulette.c index 4bda7e7dd..20ea24e64 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -445,7 +445,7 @@ static UINT32 K_ScaleItemDistance(const player_t* player, UINT32 distance, UINT8 ); // Distance is reduced based on the player's exp - distance = FixedMul(distance, min(FRACUNIT, player->exp)); + // distance = FixedMul(distance, player->exp); return distance; } @@ -1487,7 +1487,10 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // Special cases are all handled, we can now // actually calculate actual item reels. - roulette->dist = K_GetItemRouletteDistance(player, roulette->playing); + roulette->preexpdist = K_GetItemRouletteDistance(player, roulette->playing); + roulette->dist = roulette->preexpdist; + roulette->preexpuseOdds = K_FindUseodds(player, roulette); + roulette->dist = FixedMul(roulette->preexpdist, player->exp); roulette->useOdds = K_FindUseodds(player, roulette); for (i = 1; i < NUMKARTRESULTS; i++) diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index 94851cc6f..9b62c9d79 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -669,18 +669,14 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe if (player->position == 1) { player->lapPoints += 2; - player->exp += FRACUNIT/10; } else { player->lapPoints += 1; - player->exp += FRACUNIT/20; } } - else if (K_IsPlayerLosing(player)) - { - player->exp -= FRACUNIT/20; - } + + player->exp += K_GetExpAdjustment(player); K_UpdatePowerLevels(player, player->laps, false); } diff --git a/src/p_spec.c b/src/p_spec.c index efa036008..dd05ebd07 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -2111,18 +2111,14 @@ static void K_HandleLapIncrement(player_t *player) if (inDuel == false && player->position == 1) // 1st place in 1v1 uses thumbs up { player->lapPoints += 2; - player->exp += FRACUNIT/10; } else { player->lapPoints++; - player->exp += FRACUNIT/20; } } - else if (K_IsPlayerLosing(player)) - { - player->exp -= FRACUNIT/20; - } + + player->exp += K_GetExpAdjustment(player); if (player->position == 1 && !(gametyperules & GTR_CHECKPOINTS)) { From a8211b980fbc3eb3af004a71a78c47b77545a24e Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Sun, 18 Aug 2024 22:19:46 -0700 Subject: [PATCH 09/38] WIP: more EXP math --- src/k_hud.cpp | 13 +++++++++++-- src/k_kart.c | 28 ++++++++++++++++++---------- src/k_roulette.c | 2 +- src/p_inter.c | 2 ++ 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 55920f755..4ee6e65c0 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -5739,12 +5739,21 @@ static void K_drawDistributionDebugger(void) // V_DrawString((x >> FRACBITS) + 20, 50, V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); // V_DrawString((x >> FRACBITS) + 20, 58, V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); + if (rouletteData.preexpuseOdds > rouletteData.useOdds) + { + V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("items NERFED %u -> %u", rouletteData.preexpuseOdds, rouletteData.useOdds)); + } + else if (rouletteData.preexpuseOdds < rouletteData.useOdds) + { + V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("items BOOSTED %u -> %u", rouletteData.preexpuseOdds, rouletteData.useOdds)); + } + + /* V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("pre-exp dist %u", rouletteData.preexpdist)); V_DrawString((x >> FRACBITS) + 20, 18, V_SNAPTOTOP, va("pre-exp useOdds %u", rouletteData.preexpuseOdds)); V_DrawString((x >> FRACBITS) + 20, 26, V_SNAPTOTOP, va(" dist %u", rouletteData.dist)); V_DrawString((x >> FRACBITS) + 20, 34, V_SNAPTOTOP, va(" useOdds %u", rouletteData.useOdds)); - - + */ #ifndef ITEM_LIST_SIZE Z_Free(rouletteData.itemList); diff --git a/src/k_kart.c b/src/k_kart.c index a35682bdd..b9e7b0179 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4091,7 +4091,7 @@ boolean K_Overdrive(player_t *player) S_StartSound(player->mo, sfx_cdfm35); S_StartSound(player->mo, sfx_cdfm13); - player->overdrive += (player->amps)*3; + player->overdrive += (player->amps)*5; player->overshield += (player->amps)*2; player->overdrivepower = FRACUNIT; @@ -4112,7 +4112,7 @@ boolean K_DefensiveOverdrive(player_t *player) S_StartSound(player->mo, sfx_cdfm35); S_StartSound(player->mo, sfx_cdfm13); - player->overdrive += (player->amps)*2; + player->overdrive += (player->amps)*3; player->overshield += (player->amps)*2 + TICRATE*2; player->overdrivepower = FRACUNIT; @@ -14771,21 +14771,29 @@ boolean K_PlayerCanUseItem(player_t *player) fixed_t K_GetExpAdjustment(player_t *player) { - fixed_t exp_power = 1*FRACUNIT/100; // adjust to change overall xp volatility - fixed_t exp_drainrate = 995*FRACUNIT/1000; // adjust to change overall item chaos + fixed_t exp_power = 3*FRACUNIT/100; // adjust to change overall xp volatility + fixed_t exp_stablerate = 3*FRACUNIT/10; // how low is your placement before losing XP? 4*FRACUNIT/10 = top 40% of race will gain fixed_t result = 0; + INT32 live_players = 0; + + // Increase XP for each player you're beating... for (INT32 i = 0; i < MAXPLAYERS; i++) { - if (!playeringame[i] || player->spectator) + if (!playeringame[i] || players[i].spectator || player == players+i) continue; - - result -= exp_power; + + live_players++; + if (player->position < players[i].position) - { - result += FixedMul(exp_power, exp_drainrate); - } + result += exp_power; } + + // ...then take all of the XP you could possibly have earned, + // and lose it proportional to the stable rate. If you're below + // the stable threshold, this results in you losing XP. + result -= exp_power * FixedInt(FixedMul(live_players*FRACUNIT, FRACUNIT - exp_stablerate)); + return result; } diff --git a/src/k_roulette.c b/src/k_roulette.c index 20ea24e64..84ec445d4 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1490,7 +1490,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet roulette->preexpdist = K_GetItemRouletteDistance(player, roulette->playing); roulette->dist = roulette->preexpdist; roulette->preexpuseOdds = K_FindUseodds(player, roulette); - roulette->dist = FixedMul(roulette->preexpdist, player->exp); + roulette->dist = FixedMul(roulette->preexpdist, max(player->exp, FRACUNIT/2)); roulette->useOdds = K_FindUseodds(player, roulette); for (i = 1; i < NUMKARTRESULTS; i++) diff --git a/src/p_inter.c b/src/p_inter.c index 5a95788d5..2032c7930 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -3565,4 +3565,6 @@ void P_PlayerRingBurst(player_t *player, INT32 num_rings) { P_FlingBurst(player, fa, MT_DEBTSPIKE, 0, 3 * FRACUNIT / 2, i++); } + + K_DefensiveOverdrive(player); } From 1696893087ebc8b53452a1562692726d46a52bb5 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Mon, 19 Aug 2024 07:49:26 -0400 Subject: [PATCH 10/38] Ringbox award fix oops --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index b9e7b0179..8b1102d57 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12703,7 +12703,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) else { UINT32 behind = K_GetItemRouletteDistance(player, player->itemRoulette.playing); - behind = FixedMul(behind, K_GetExpAdjustment(player)); + behind = FixedMul(behind, player->exp); UINT32 behindMulti = behind / 500; behindMulti = min(behindMulti, 60); award = award * (behindMulti + 10) / 10; From 5d2f5ac6c7ab097bdfb1b41f91ea8fe4e75ecbb6 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Mon, 19 Aug 2024 18:34:08 -0400 Subject: [PATCH 11/38] unfuck savegame for debug --- src/d_player.h | 4 ++-- src/p_saveg.c | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 565192267..0bc8f048f 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -495,9 +495,9 @@ struct itemroulette_t SINT8 *itemList; #endif - UINT8 useOdds; + UINT8 preexpuseOdds, useOdds; UINT8 playing, exiting; - UINT32 preexpdist, preexpuseOdds, dist, baseDist; + UINT32 preexpdist, dist, baseDist; UINT32 firstDist, secondDist; UINT32 secondToFirst; diff --git a/src/p_saveg.c b/src/p_saveg.c index a9e811961..a9a2a1785 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -744,7 +744,9 @@ static void P_NetArchivePlayers(savebuffer_t *save) } #endif + WRITEUINT8(save->p, players[i].itemRoulette.preexpuseOdds); WRITEUINT8(save->p, players[i].itemRoulette.useOdds); + WRITEUINT32(save->p, players[i].itemRoulette.preexpdist); WRITEUINT32(save->p, players[i].itemRoulette.dist); WRITEUINT32(save->p, players[i].itemRoulette.index); WRITEUINT8(save->p, players[i].itemRoulette.sound); @@ -1368,7 +1370,9 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) } #endif + players[i].itemRoulette.preexpuseOdds = READUINT8(save->p); players[i].itemRoulette.useOdds = READUINT8(save->p); + players[i].itemRoulette.preexpdist = READUINT32(save->p); players[i].itemRoulette.dist = READUINT32(save->p); players[i].itemRoulette.index = (size_t)READUINT32(save->p); players[i].itemRoulette.sound = READUINT8(save->p); From 155fe22bd75dd757abf149ca415b940e407d6b75 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Mon, 19 Aug 2024 19:48:02 -0400 Subject: [PATCH 12/38] K_drawDistributionDebugger is NOT netsafe --- src/k_hud.cpp | 79 ++++++++++++++++++++++-------------------------- src/k_hud.h | 2 +- src/k_roulette.c | 11 +++++++ 3 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 4ee6e65c0..41665e707 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -5698,46 +5698,46 @@ static void K_drawDistributionDebugger(void) K_FillItemRouletteData(stplyr, &rouletteData, false); - // for (i = 0; i < rouletteData.itemListLen; i++) - // { - // const kartitems_t item = static_cast(rouletteData.itemList[i]); - // UINT8 amount = 1; + for (i = 0; i < rouletteData.itemListLen; i++) + { + const kartitems_t item = static_cast(rouletteData.itemList[i]); + UINT8 amount = 1; - // if (y > (BASEVIDHEIGHT << FRACBITS) - space - pad) - // { - // x += space; - // y = -pad; - // } + if (y > (BASEVIDHEIGHT << FRACBITS) - space - pad) + { + x += space; + y = -pad; + } - // V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, - // K_GetSmallStaticCachedItemPatch(item), NULL); + V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, + K_GetSmallStaticCachedItemPatch(item), NULL); - // // Display amount for multi-items - // amount = K_ItemResultToAmount(item); - // if (amount > 1) - // { - // V_DrawStringScaled( - // x + (18 * scale), - // y + (23 * scale), - // scale, FRACUNIT, FRACUNIT, - // V_SNAPTOTOP, - // NULL, HU_FONT, - // va("x%d", amount) - // ); - // } + // Display amount for multi-items + amount = K_ItemResultToAmount(item); + if (amount > 1) + { + V_DrawStringScaled( + x + (18 * scale), + y + (23 * scale), + scale, FRACUNIT, FRACUNIT, + V_SNAPTOTOP, + NULL, HU_FONT, + va("x%d", amount) + ); + } - // y += space; - // } + y += space; + } - // V_DrawString((x >> FRACBITS) + 20, 2, V_SNAPTOTOP, va("useOdds[%u]", rouletteData.useOdds)); - // V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); + V_DrawString((x >> FRACBITS) + 20, 2, V_SNAPTOTOP, va("useOdds[%u]", rouletteData.useOdds)); + V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); - // V_DrawString((x >> FRACBITS) + 20, 22, V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist)); - // V_DrawString((x >> FRACBITS) + 20, 30, V_SNAPTOTOP, va("dist = %u", rouletteData.dist)); + V_DrawString((x >> FRACBITS) + 20, 22, V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist)); + V_DrawString((x >> FRACBITS) + 20, 30, V_SNAPTOTOP, va("dist = %u", rouletteData.dist)); - // V_DrawString((x >> FRACBITS) + 20, 42, V_SNAPTOTOP, va("firstDist = %u", rouletteData.firstDist)); - // V_DrawString((x >> FRACBITS) + 20, 50, V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); - // V_DrawString((x >> FRACBITS) + 20, 58, V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); + V_DrawString((x >> FRACBITS) + 20, 42, V_SNAPTOTOP, va("firstDist = %u", rouletteData.firstDist)); + V_DrawString((x >> FRACBITS) + 20, 50, V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); + V_DrawString((x >> FRACBITS) + 20, 58, V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); if (rouletteData.preexpuseOdds > rouletteData.useOdds) { @@ -5748,13 +5748,6 @@ static void K_drawDistributionDebugger(void) V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("items BOOSTED %u -> %u", rouletteData.preexpuseOdds, rouletteData.useOdds)); } - /* - V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("pre-exp dist %u", rouletteData.preexpdist)); - V_DrawString((x >> FRACBITS) + 20, 18, V_SNAPTOTOP, va("pre-exp useOdds %u", rouletteData.preexpuseOdds)); - V_DrawString((x >> FRACBITS) + 20, 26, V_SNAPTOTOP, va(" dist %u", rouletteData.dist)); - V_DrawString((x >> FRACBITS) + 20, 34, V_SNAPTOTOP, va(" useOdds %u", rouletteData.useOdds)); - */ - #ifndef ITEM_LIST_SIZE Z_Free(rouletteData.itemList); #endif @@ -6060,7 +6053,7 @@ void K_ClearPersistentMessages() } // Return value can be used for "paired" splitscreen messages, true = was displayed -void K_AddMessageForPlayer(player_t *player, const char *msg, boolean interrupt, boolean persist) +void K_AddMessageForPlayer(const player_t *player, const char *msg, boolean interrupt, boolean persist) { if (!player) return; @@ -6525,11 +6518,11 @@ void K_drawKartHUD(void) K_drawInput(); } - // if (cv_kartdebugdistribution.value) + if (cv_kartdebugdistribution.value) K_drawDistributionDebugger(); // temp debug - V_DrawString(8, 2, V_SNAPTOTOP, va("Exp/Dist mult: %.2f", FixedToFloat(stplyr->exp))); + V_DrawSmallString(8, 2, V_SNAPTOTOP, va("Exp/Dist mult: %.2f", FixedToFloat(stplyr->exp))); // V_DrawSmallString(8, 4, V_SNAPTOTOP, va("Exp/Dist mult: %.2f", FixedToFloat(stplyr->exp))); if (cv_kartdebugnodes.value) diff --git a/src/k_hud.h b/src/k_hud.h index 07cea4774..8d2b38819 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -101,7 +101,7 @@ extern patch_t *kp_facenum[MAXPLAYERS+1]; extern patch_t *kp_unknownminimap; void K_AddMessage(const char *msg, boolean interrupt, boolean persist); -void K_AddMessageForPlayer(player_t *player, const char *msg, boolean interrupt, boolean persist); +void K_AddMessageForPlayer(const player_t *player, const char *msg, boolean interrupt, boolean persist); void K_ClearPersistentMessages(void); void K_ClearPersistentMessageForPlayer(player_t *player); void K_TickMessages(void); diff --git a/src/k_roulette.c b/src/k_roulette.c index 84ec445d4..685346dc4 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1493,6 +1493,17 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet roulette->dist = FixedMul(roulette->preexpdist, max(player->exp, FRACUNIT/2)); roulette->useOdds = K_FindUseodds(player, roulette); + if (roulette->preexpuseOdds > roulette->useOdds) + { + K_AddMessageForPlayer(player, va("items NERFED %u -> %u", roulette->preexpuseOdds, roulette->useOdds), true, false); + CONS_Printf("%s items NERFED %u -> %u", player_names[player - players], roulette->preexpuseOdds, roulette->useOdds); + } + else if (roulette->preexpuseOdds < roulette->useOdds) + { + K_AddMessageForPlayer(player, va("items BOOSTED %u -> %u", roulette->preexpuseOdds, roulette->useOdds), true, false); + CONS_Printf("%s items BOOSTED %u -> %u", player_names[player - players], roulette->preexpuseOdds, roulette->useOdds); + } + for (i = 1; i < NUMKARTRESULTS; i++) { spawnChance[i] = ( From 78a678b8ca5b7ddf19df1eb8fda500146ba8b1ce Mon Sep 17 00:00:00 2001 From: Ashnal Date: Mon, 19 Aug 2024 19:54:28 -0400 Subject: [PATCH 13/38] Revert dist debug --- src/k_hud.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 41665e707..54ac53585 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -5739,15 +5739,6 @@ static void K_drawDistributionDebugger(void) V_DrawString((x >> FRACBITS) + 20, 50, V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); V_DrawString((x >> FRACBITS) + 20, 58, V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); - if (rouletteData.preexpuseOdds > rouletteData.useOdds) - { - V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("items NERFED %u -> %u", rouletteData.preexpuseOdds, rouletteData.useOdds)); - } - else if (rouletteData.preexpuseOdds < rouletteData.useOdds) - { - V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("items BOOSTED %u -> %u", rouletteData.preexpuseOdds, rouletteData.useOdds)); - } - #ifndef ITEM_LIST_SIZE Z_Free(rouletteData.itemList); #endif From 88614106928874a1632add918e699118b8dd0ff3 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Mon, 19 Aug 2024 19:54:42 -0400 Subject: [PATCH 14/38] Missing newlines --- src/k_roulette.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 685346dc4..f3adefec0 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1496,12 +1496,12 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet if (roulette->preexpuseOdds > roulette->useOdds) { K_AddMessageForPlayer(player, va("items NERFED %u -> %u", roulette->preexpuseOdds, roulette->useOdds), true, false); - CONS_Printf("%s items NERFED %u -> %u", player_names[player - players], roulette->preexpuseOdds, roulette->useOdds); + CONS_Printf("%s items NERFED %u -> %u\n", player_names[player - players], roulette->preexpuseOdds, roulette->useOdds); } else if (roulette->preexpuseOdds < roulette->useOdds) { K_AddMessageForPlayer(player, va("items BOOSTED %u -> %u", roulette->preexpuseOdds, roulette->useOdds), true, false); - CONS_Printf("%s items BOOSTED %u -> %u", player_names[player - players], roulette->preexpuseOdds, roulette->useOdds); + CONS_Printf("%s items BOOSTED %u -> %u\n", player_names[player - players], roulette->preexpuseOdds, roulette->useOdds); } for (i = 1; i < NUMKARTRESULTS; i++) From 0dfdf563f3957c1c4adb92efd34b8bccb70aef61 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Mon, 19 Aug 2024 16:57:36 -0700 Subject: [PATCH 15/38] That's not how fixed math works, idiot --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 8b1102d57..263306c6e 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -14792,7 +14792,7 @@ fixed_t K_GetExpAdjustment(player_t *player) // ...then take all of the XP you could possibly have earned, // and lose it proportional to the stable rate. If you're below // the stable threshold, this results in you losing XP. - result -= exp_power * FixedInt(FixedMul(live_players*FRACUNIT, FRACUNIT - exp_stablerate)); + result -= FixedMul(exp_power, FixedMul(live_players*FRACUNIT, FRACUNIT - exp_stablerate)); return result; } From 6287ee485aea5daaeca84e3a3e5c7ede34838c02 Mon Sep 17 00:00:00 2001 From: Ashnal Date: Mon, 19 Aug 2024 21:49:18 -0400 Subject: [PATCH 16/38] cap ring award exp penalty --- src/k_kart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 8b1102d57..cc7856ce4 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12703,7 +12703,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) else { UINT32 behind = K_GetItemRouletteDistance(player, player->itemRoulette.playing); - behind = FixedMul(behind, player->exp); + behind = FixedMul(behind, max(player->exp, FRACUNIT/2)); UINT32 behindMulti = behind / 500; behindMulti = min(behindMulti, 60); award = award * (behindMulti + 10) / 10; From b51f4d28a91632399601db9a0532026d156cc1ef Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 20 Aug 2024 04:22:00 -0700 Subject: [PATCH 17/38] WIP - Dynamic roulette --- src/d_player.h | 1 - src/k_hud.cpp | 1 - src/k_kart.c | 28 +--- src/k_roulette.c | 342 ++++++++++++++++++++++++++++++----------------- src/p_saveg.c | 4 - 5 files changed, 218 insertions(+), 158 deletions(-) diff --git a/src/d_player.h b/src/d_player.h index 0bc8f048f..214722a5c 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -495,7 +495,6 @@ struct itemroulette_t SINT8 *itemList; #endif - UINT8 preexpuseOdds, useOdds; UINT8 playing, exiting; UINT32 preexpdist, dist, baseDist; UINT32 firstDist, secondDist; diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 54ac53585..b595b00a0 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -5729,7 +5729,6 @@ static void K_drawDistributionDebugger(void) y += space; } - V_DrawString((x >> FRACBITS) + 20, 2, V_SNAPTOTOP, va("useOdds[%u]", rouletteData.useOdds)); V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); V_DrawString((x >> FRACBITS) + 20, 22, V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist)); diff --git a/src/k_kart.c b/src/k_kart.c index 263306c6e..6e2bd674b 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7413,32 +7413,8 @@ void K_DropHnextList(player_t *player) SINT8 K_GetTotallyRandomResult(UINT8 useodds) { - INT32 spawnchance[NUMKARTRESULTS]; - INT32 totalspawnchance = 0; - INT32 i; - - memset(spawnchance, 0, sizeof (spawnchance)); - - for (i = 1; i < NUMKARTRESULTS; i++) - { - // Avoid calling K_FillItemRouletteData since that - // function resets PR_ITEM_ROULETTE. - spawnchance[i] = ( - totalspawnchance += K_KartGetItemOdds(NULL, NULL, useodds, i) - ); - } - - if (totalspawnchance > 0) - { - totalspawnchance = P_RandomKey(PR_ITEM_ROULETTE, totalspawnchance); - for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); - } - else - { - i = KITEM_SAD; - } - - return i; + // TODO + return 0; } mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount) diff --git a/src/k_roulette.c b/src/k_roulette.c index f3adefec0..0ea262303 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -651,6 +651,9 @@ typedef struct { --------------------------------------------------*/ static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette) { + // TODO + return newOdds; + // None if this applies outside of Race modes (for now?) if ((gametyperules & GTR_CIRCUIT) == 0) { @@ -696,6 +699,7 @@ static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemcondition See header file for description. --------------------------------------------------*/ + INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) { boolean bot = false; @@ -888,116 +892,6 @@ INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, return newOdds; } -/*-------------------------------------------------- - static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulette) - - Gets which item bracket the player is in. - This can be adjusted depending on which - items being turned off. - - Input Arguments:- - player - The player the roulette is for. - roulette - The item roulette data. - - Return:- - The item bracket the player is in, as an - index to the array. ---------------------------------------------------*/ -static UINT8 K_FindUseodds(const player_t *player, itemroulette_t *const roulette) -{ - UINT8 i; - UINT8 useOdds = 0; - UINT8 distTable[14]; - UINT8 distLen = 0; - UINT8 totalSize = 0; - boolean oddsValid[8]; - - for (i = 0; i < 8; i++) - { - UINT8 j; - - if (gametype == GT_BATTLE && i > 1) - { - oddsValid[i] = false; - continue; - } - else if (specialstageinfo.valid == true && i > 3) - { - oddsValid[i] = false; - continue; - } - - for (j = 1; j < NUMKARTRESULTS; j++) - { - if (K_KartGetItemOdds(player, roulette, i, j) > 0) - { - break; - } - } - - oddsValid[i] = (j < NUMKARTRESULTS); - } - -#define SETUPDISTTABLE(odds, num) \ - totalSize += num; \ - if (oddsValid[odds]) \ - for (i = num; i; --i) \ - distTable[distLen++] = odds; - - if (gametype == GT_BATTLE) // Battle Mode - { - useOdds = 0; - } - else - { - if (specialstageinfo.valid == true) // Special Stages - { - SETUPDISTTABLE(0,2); - SETUPDISTTABLE(1,2); - SETUPDISTTABLE(2,3); - SETUPDISTTABLE(3,1); - } - else - { - SETUPDISTTABLE(0,1); - SETUPDISTTABLE(1,1); - SETUPDISTTABLE(2,1); - SETUPDISTTABLE(3,2); - SETUPDISTTABLE(4,2); - SETUPDISTTABLE(5,3); - SETUPDISTTABLE(6,3); - SETUPDISTTABLE(7,1); - } - - for (i = 0; i < totalSize; i++) - { - fixed_t pos = 0; - fixed_t dist = 0; - UINT8 index = 0; - - if (i == totalSize-1) - { - useOdds = distTable[distLen - 1]; - break; - } - - pos = ((i << FRACBITS) * distLen) / totalSize; - dist = FixedMul(DISTVAR << FRACBITS, pos) >> FRACBITS; - index = FixedInt(FixedRound(pos)); - - if (roulette->dist <= (unsigned)dist) - { - useOdds = distTable[index]; - break; - } - } - } - -#undef SETUPDISTTABLE - - return useOdds; -} - /*-------------------------------------------------- static boolean K_ForcedSPB(const player_t *player, itemroulette_t *const roulette) @@ -1094,7 +988,6 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->itemListLen = 0; roulette->index = 0; - roulette->useOdds = UINT8_MAX; roulette->baseDist = roulette->dist = 0; roulette->playing = roulette->exiting = 0; roulette->firstDist = roulette->secondDist = UINT32_MAX; @@ -1328,7 +1221,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet UINT8 numItems = 0; kartitems_t singleItem = KITEM_SAD; - size_t i; + size_t i, j; K_InitRoulette(roulette); @@ -1417,7 +1310,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // every item in the game! // Create the same item reel given the same inputs. - P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); + // P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); for (i = 1; i < NUMKARTRESULTS; i++) { @@ -1489,40 +1382,235 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // actually calculate actual item reels. roulette->preexpdist = K_GetItemRouletteDistance(player, roulette->playing); roulette->dist = roulette->preexpdist; - roulette->preexpuseOdds = K_FindUseodds(player, roulette); roulette->dist = FixedMul(roulette->preexpdist, max(player->exp, FRACUNIT/2)); - roulette->useOdds = K_FindUseodds(player, roulette); - if (roulette->preexpuseOdds > roulette->useOdds) + // == EVERYTHING FUCKED BELOW THIS LINE + + UINT32 targetpower = roulette->dist; // fill roulette with items around this value! + UINT32 powers[NUMKARTRESULTS]; // how strong is each item? + UINT32 deltas[NUMKARTRESULTS]; // how different is that strength from target? + UINT32 candidates[NUMKARTRESULTS]; // how many of this item should we try to insert? + UINT32 dupetolerance[NUMKARTRESULTS]; // how willing are we to select this item after already selecting it? higher values = lower dupe penalty + UINT32 duplicates[NUMKARTRESULTS]; // how many copies of this item are already waiting to be inserted? + boolean permit[NUMKARTRESULTS]; // is this item allowed? + boolean firstonly[NUMKARTRESULTS]; // is this item only first? + boolean nofirst[NUMKARTRESULTS]; // is this item never for first? + + // Items permissible? + for (i = 1; i < NUMKARTRESULTS; i++) { - K_AddMessageForPlayer(player, va("items NERFED %u -> %u", roulette->preexpuseOdds, roulette->useOdds), true, false); - CONS_Printf("%s items NERFED %u -> %u\n", player_names[player - players], roulette->preexpuseOdds, roulette->useOdds); + permit[i] = true; + + boolean notNearEnd = false; + boolean powerItem = false; + boolean cooldownOnStart = false; + + switch (i) + { + case KITEM_BANANA: + case KITEM_EGGMAN: + case KITEM_SUPERRING: + { + notNearEnd = true; + break; + } + + case KITEM_ROCKETSNEAKER: + case KITEM_JAWZ: + case KITEM_LANDMINE: + case KITEM_DROPTARGET: + case KITEM_BALLHOG: + case KRITEM_TRIPLESNEAKER: + case KRITEM_TRIPLEORBINAUT: + case KRITEM_QUADORBINAUT: + case KRITEM_DUALJAWZ: + { + powerItem = true; + break; + } + + case KITEM_HYUDORO: + case KRITEM_TRIPLEBANANA: + { + powerItem = true; + notNearEnd = true; + break; + } + + case KITEM_INVINCIBILITY: + case KITEM_MINE: + case KITEM_GROW: + case KITEM_BUBBLESHIELD: + { + cooldownOnStart = true; + powerItem = true; + break; + } + + case KITEM_FLAMESHIELD: + case KITEM_GARDENTOP: + { + cooldownOnStart = true; + powerItem = true; + notNearEnd = true; + break; + } + + case KITEM_SPB: + { + cooldownOnStart = true; + notNearEnd = true; + // TODO forcing, just disable for now + permit[i] = false; + break; + } + + case KITEM_SHRINK: + { + cooldownOnStart = true; + powerItem = true; + notNearEnd = true; + break; + } + + case KITEM_LIGHTNINGSHIELD: + { + cooldownOnStart = true; + powerItem = true; + if ((gametyperules & GTR_CIRCUIT) && spbplace != -1) + { + permit[i] = false; + } + break; + } + } + + if (cooldownOnStart && (leveltime < (30*TICRATE) + starttime)) + permit[i] = false; + if (notNearEnd && (roulette != NULL && roulette->baseDist < ENDDIST)) + permit[i] = false; + } - else if (roulette->preexpuseOdds < roulette->useOdds) + + // invent some bullshit Ps based on existing useodds, temp + for (i = 1; i < NUMKARTRESULTS; i++) { - K_AddMessageForPlayer(player, va("items BOOSTED %u -> %u", roulette->preexpuseOdds, roulette->useOdds), true, false); - CONS_Printf("%s items BOOSTED %u -> %u\n", player_names[player - players], roulette->preexpuseOdds, roulette->useOdds); + powers[i] = 0; + dupetolerance[i] = 0; + UINT32 entries = 0; + firstonly[i] = true; + nofirst[i] = true; + for (j = 0; j < 8; j++) + { + if (K_KartItemOddsRace[i-1][j] > 0) + { + powers[i] += (j+1) * DISTVAR * K_KartItemOddsRace[i-1][j]; + entries += K_KartItemOddsRace[i-1][j]; + if (j > 0) + firstonly[i] = false; + if (j == 0) + nofirst[i] = false; + } + } + + dupetolerance[i] += entries; + + if (entries) + powers[i] /= entries; + + // CONS_Printf("%s: %d - %d - FO %d - NF %d\n", cv_items[i-1].name, powers[i], dupetolerance[i], firstonly[i], nofirst[i]); } + // temp - null stuff that doesn't have odds, then distance correct + for (i = 1; i < NUMKARTRESULTS; i++) + { + if (powers[i] == 0) + permit[i] = false; + else + powers[i] -= DISTVAR; + } + + // stupid hack for test run / no waypoints + if (player->position == 1) + targetpower = 0; + + // Starting deltas + for (i = 1; i < NUMKARTRESULTS; i++) + { + candidates[i] = 0; + deltas[i] = min(targetpower - powers[i], powers[i] - targetpower); + // CONS_Printf("starting delta for %s is %d\n", cv_items[i-1].name, deltas[i]); + } + + // let's start finding items to list + for (i = 0; i < 15; i++) + { + UINT32 lowestdelta = INT32_MAX; + size_t lowestindex = 0; + + // CONS_Printf("LOOP %d\n", i); + + // check each kartitem to see which is the best fit, + // based on what's closest to our target power + // (but ignore items that aren't allowed now) + for (j = 1; j < NUMKARTRESULTS; j++) + { + // CONS_Printf("precheck %s, FO %d NF %d CD %d\n", cv_items[j-1].name, firstonly[j], nofirst[j], K_GetItemCooldown(j)); + + if (!permit[j]) + continue; + if (K_GetItemCooldown(j)) + continue; + if (!K_ItemEnabled(j)) + continue; + if (firstonly[j] && player->position > 1) + continue; + if (nofirst[j] && player->position == 1) + continue; + + // CONS_Printf("checking %s, delta %d\n", cv_items[j-1].name, deltas[j]); + + if (lowestdelta > deltas[j]) + { + lowestindex = j; + lowestdelta = deltas[j]; + } + + } + + // couldn't find an item? goodbye lol + if (lowestindex == 0) + break; + + // otherwise, prep it to be added and give it a duplicaton penalty, + // so that a different item is more likely to be inserted next + candidates[lowestindex]++; + duplicates[lowestindex]++; + deltas[lowestindex] += (DISTVAR*6/dupetolerance[lowestindex])^(duplicates[lowestindex]); + + // CONS_Printf("added %s with candidates %d\n", cv_items[lowestindex-1].name, candidates[lowestindex]); + } + + // set up the list indices used to random-shuffle the ro ulette for (i = 1; i < NUMKARTRESULTS; i++) { spawnChance[i] = ( - totalSpawnChance += K_KartGetItemOdds(player, roulette, roulette->useOdds, i) + totalSpawnChance += candidates[i] ); } if (totalSpawnChance == 0) { - // This shouldn't happen, but if it does, early exit. - // Maybe can happen if you enable multiple items for - // another gametype, so we give the singleItem as a fallback. + // why did this fucking happen LOL + // don't crash K_AddItemToReel(player, roulette, singleItem); return; } // Create the same item reel given the same inputs. - P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); + // P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); + // and insert all of our candidates into the roulette in a random order while (totalSpawnChance > 0) { rngRoll = P_RandomKey(PR_ITEM_ROULETTE, totalSpawnChance); @@ -1531,6 +1619,8 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet continue; } + // CONS_Printf("adding %s, tsp %d\n", cv_items[i-1].name, totalSpawnChance); + K_AddItemToReel(player, roulette, i); for (; i < NUMKARTRESULTS; i++) diff --git a/src/p_saveg.c b/src/p_saveg.c index a9a2a1785..c30c741f2 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -744,8 +744,6 @@ static void P_NetArchivePlayers(savebuffer_t *save) } #endif - WRITEUINT8(save->p, players[i].itemRoulette.preexpuseOdds); - WRITEUINT8(save->p, players[i].itemRoulette.useOdds); WRITEUINT32(save->p, players[i].itemRoulette.preexpdist); WRITEUINT32(save->p, players[i].itemRoulette.dist); WRITEUINT32(save->p, players[i].itemRoulette.index); @@ -1370,8 +1368,6 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) } #endif - players[i].itemRoulette.preexpuseOdds = READUINT8(save->p); - players[i].itemRoulette.useOdds = READUINT8(save->p); players[i].itemRoulette.preexpdist = READUINT32(save->p); players[i].itemRoulette.dist = READUINT32(save->p); players[i].itemRoulette.index = (size_t)READUINT32(save->p); From b388c48be78ca31ddecb4598f9553bec210d5604 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 20 Aug 2024 17:57:40 -0700 Subject: [PATCH 18/38] WIP - Dynamic Roulette --- src/cvars.cpp | 2 +- src/k_hud.cpp | 16 +- src/k_hud.h | 2 + src/k_roulette.c | 534 ++++++++++++++++++++++++++++++++++------------- 4 files changed, 403 insertions(+), 151 deletions(-) diff --git a/src/cvars.cpp b/src/cvars.cpp index 048613394..48435238f 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -819,7 +819,7 @@ consvar_t cv_fuzz = OnlineCheat("fuzz", "Off").on_off().description("Human playe consvar_t cv_kartdebugamount = OnlineCheat("debugitemamount", "1").min_max(1, 255).description("If debugitem, give multiple copies of an item"); consvar_t cv_kartdebugbots = OnlineCheat("debugbots", "Off").on_off().description("Bot AI debugger"); -consvar_t cv_kartdebugdistribution = OnlineCheat("debugitemodds", "Off").on_off().description("Show items that the roulette can roll"); +consvar_t cv_kartdebugdistribution = OnlineCheat("debugitemodds", "0").min_max(0, 2).description("Show items that the roulette can roll"); consvar_t cv_kartdebughuddrop = OnlineCheat("debugitemdrop", "Off").on_off().description("Players drop paper items when damaged in any way"); consvar_t cv_kartdebugbotwhip = OnlineCheat("debugbotwhip", "Off").on_off().description("Disable bot ring and item pickups"); diff --git a/src/k_hud.cpp b/src/k_hud.cpp index b595b00a0..b903e8114 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -1061,7 +1061,7 @@ static patch_t *K_GetCachedItemPatch(INT32 item, UINT8 offset) return NULL; } -static patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item) +patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item) { UINT8 offset; @@ -5698,6 +5698,8 @@ static void K_drawDistributionDebugger(void) K_FillItemRouletteData(stplyr, &rouletteData, false); + return; + for (i = 0; i < rouletteData.itemListLen; i++) { const kartitems_t item = static_cast(rouletteData.itemList[i]); @@ -5729,14 +5731,14 @@ static void K_drawDistributionDebugger(void) y += space; } - V_DrawString((x >> FRACBITS) + 20, 10, V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); + V_DrawRightAlignedString(320 - (x >> FRACBITS), 10, V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); - V_DrawString((x >> FRACBITS) + 20, 22, V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist)); - V_DrawString((x >> FRACBITS) + 20, 30, V_SNAPTOTOP, va("dist = %u", rouletteData.dist)); + V_DrawRightAlignedString(320 - (x >> FRACBITS), 22, V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist)); + V_DrawRightAlignedString(320 - (x >> FRACBITS), 30, V_SNAPTOTOP, va("dist = %u", rouletteData.dist)); - V_DrawString((x >> FRACBITS) + 20, 42, V_SNAPTOTOP, va("firstDist = %u", rouletteData.firstDist)); - V_DrawString((x >> FRACBITS) + 20, 50, V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); - V_DrawString((x >> FRACBITS) + 20, 58, V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); + V_DrawRightAlignedString(320 - (x >> FRACBITS), 42, V_SNAPTOTOP, va("firstDist = %u", rouletteData.firstDist)); + V_DrawRightAlignedString(320 - (x >> FRACBITS), 50, V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); + V_DrawRightAlignedString(320 - (x >> FRACBITS), 58, V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); #ifndef ITEM_LIST_SIZE Z_Free(rouletteData.itemList); diff --git a/src/k_hud.h b/src/k_hud.h index 8d2b38819..74ed5d61d 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -106,6 +106,8 @@ void K_ClearPersistentMessages(void); void K_ClearPersistentMessageForPlayer(player_t *player); void K_TickMessages(void); +patch_t *K_GetSmallStaticCachedItemPatch(kartitems_t item); + typedef enum { PLAYERTAG_NONE, diff --git a/src/k_roulette.c b/src/k_roulette.c index 0ea262303..9959b6719 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -51,6 +51,7 @@ #include "k_objects.h" #include "k_grandprix.h" #include "k_specialstage.h" +#include "k_hud.h" // distribution debugger // Magic number distance for use with item roulette tiers #define DISTVAR (2048) @@ -75,6 +76,111 @@ #define ROULETTE_SPEED_TIMEATTACK (9) #define ROULETTE_SPEED_VERSUS_SLOWEST (12) +static UINT32 K_DynamicItemOddsRace[NUMKARTRESULTS-1][2] = +{ + // distance, duplication tolerance + {43, 9}, // sneaker + {73, 12}, // rocketsneaker + {70, 19}, // invincibility + {18, 6}, // banana + {17, 3}, // eggmark + {21, 14}, // orbinaut + {26, 7}, // jawz + {29, 8}, // mine + {10, 3}, // landmine + {35, 4}, // ballhog + {68, 6}, // selfpropelledbomb + {58, 7}, // grow + {71, 8}, // shrink + {10, 1}, // lightningshield + {30, 4}, // bubbleshield + {76, 9}, // flameshield + {10, 3}, // hyudoro + {0, 0}, // pogospring + {17, 4}, // superring + {0, 0}, // kitchensink + {10, 3}, // droptarget + {53, 5}, // gardentop + {0, 0}, // gachabom + {44, 9}, // dualsneaker + {61, 12}, // triplesneaker + {25, 2}, // triplebanana + {30, 1}, // tripleorbinaut + {40, 2}, // quadorbinaut + {40, 4}, // dualjawz + {0, 0}, // triplegachabom +}; + +static UINT32 K_DynamicItemOddsBattle[NUMKARTRESULTS-1][2] = +{ + // distance, duplication tolerance + {20, 1}, // sneaker + {0, 0}, // rocketsneaker + {20, 1}, // invincibility + {0, 0}, // banana + {0, 0}, // eggmark + {10, 2}, // orbinaut + {12, 4}, // jawz + {13, 3}, // mine + {0, 0}, // landmine + {13, 3}, // ballhog + {0, 0}, // selfpropelledbomb + {15, 2}, // grow + {0, 0}, // shrink + {0, 0}, // lightningshield + {10, 1}, // bubbleshield + {0, 0}, // flameshield + {0, 0}, // hyudoro + {0, 0}, // pogospring + {0, 0}, // superring + {0, 0}, // kitchensink + {0, 0}, // droptarget + {0, 0}, // gardentop + {10, 5}, // gachabom + {0, 0}, // dualsneaker + {20, 1}, // triplesneaker + {0, 0}, // triplebanana + {10, 2}, // tripleorbinaut + {13, 3}, // quadorbinaut + {13, 3}, // dualjawz + {10, 2}, // triplegachabom +}; + +static UINT32 K_DynamicItemOddsSpecial[NUMKARTRESULTS-1][2] = +{ + // distance, duplication tolerance + {15, 2}, // sneaker + {0, 0}, // rocketsneaker + {0, 0}, // invincibility + {0, 0}, // banana + {0, 0}, // eggmark + {20, 3}, // orbinaut + {15, 2}, // jawz + {0, 0}, // mine + {0, 0}, // landmine + {0, 0}, // ballhog + {70, 1}, // selfpropelledbomb + {0, 0}, // grow + {0, 0}, // shrink + {0, 0}, // lightningshield + {0, 0}, // bubbleshield + {0, 0}, // flameshield + {0, 0}, // hyudoro + {0, 0}, // pogospring + {0, 0}, // superring + {0, 0}, // kitchensink + {0, 0}, // droptarget + {0, 0}, // gardentop + {0, 0}, // gachabom + {35, 2}, // dualsneaker + {0, 0}, // triplesneaker + {0, 0}, // triplebanana + {35, 2}, // tripleorbinaut + {0, 0}, // quadorbinaut + {35, 2}, // dualjawz + {0, 0}, // triplegachabom +}; + static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = { { 0, 0, 2, 3, 4, 0, 0, 0 }, // Sneaker @@ -1207,6 +1313,169 @@ static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST + FixedMul(ROULETTE_SPEED_SLOWEST - ROULETTE_SPEED_FASTEST, total); } +static boolean K_IsItemFirstOnly(kartitems_t item) +{ + switch (item) + { + case KITEM_LANDMINE: + case KITEM_LIGHTNINGSHIELD: + case KITEM_HYUDORO: + case KITEM_DROPTARGET: + return true; + default: + return false; + } +} + +static boolean K_IsItemFirstPermitted(kartitems_t item) +{ + if (K_IsItemFirstOnly(item)) + return true; + + switch (item) + { + case KITEM_BANANA: + case KITEM_EGGMAN: + case KITEM_ORBINAUT: + case KITEM_SUPERRING: + return true; + default: + return false; + } +} + +// Which items are disallowed for THIS player? +static boolean K_ShouldPlayerAllowItem(kartitems_t item, const player_t *player) +{ + if (!(gametyperules & GTR_CIRCUIT)) + return true; + if (specialstageinfo.valid == true) + return true; + + if (player->position == 1) + return K_IsItemFirstPermitted(item); + else + return !K_IsItemFirstOnly(item); +} + +static boolean K_IsItemPower(kartitems_t item) +{ + switch (item) + { + case KITEM_ROCKETSNEAKER: + case KITEM_JAWZ: + case KITEM_LANDMINE: + case KITEM_DROPTARGET: + case KITEM_BALLHOG: + case KRITEM_TRIPLESNEAKER: + case KRITEM_TRIPLEORBINAUT: + case KRITEM_QUADORBINAUT: + case KRITEM_DUALJAWZ: + case KITEM_HYUDORO: + case KRITEM_TRIPLEBANANA: + case KITEM_FLAMESHIELD: + case KITEM_GARDENTOP: + case KITEM_SHRINK: + case KITEM_LIGHTNINGSHIELD: + return true; + default: + return false; + } +} + +// Which items are disallowed for ALL players? +static boolean K_ShouldAllowItem(kartitems_t item, const itemroulette_t *roulette) +{ + if (!(gametyperules & GTR_CIRCUIT)) + return true; + if (specialstageinfo.valid == true) + return true; + + boolean notNearEnd = false; + boolean cooldownOnStart = false; + + switch (item) + { + case KITEM_BANANA: + case KITEM_EGGMAN: + case KITEM_SUPERRING: + { + notNearEnd = true; + break; + } + + case KITEM_HYUDORO: + case KRITEM_TRIPLEBANANA: + { + notNearEnd = true; + break; + } + + case KITEM_INVINCIBILITY: + case KITEM_MINE: + case KITEM_GROW: + case KITEM_BUBBLESHIELD: + { + cooldownOnStart = true; + break; + } + + case KITEM_FLAMESHIELD: + case KITEM_GARDENTOP: + { + cooldownOnStart = true; + notNearEnd = true; + break; + } + + case KITEM_SPB: + { + cooldownOnStart = true; + notNearEnd = true; + // TODO forcing, just disable for now + return false; + break; + } + + case KITEM_SHRINK: + { + cooldownOnStart = true; + notNearEnd = true; + break; + } + + case KITEM_LIGHTNINGSHIELD: + { + cooldownOnStart = true; + if ((gametyperules & GTR_CIRCUIT) && spbplace != -1) + { + return false; + } + break; + } + + default: + break; + } + + if (cooldownOnStart && (leveltime < (30*TICRATE) + starttime)) + return false; + if (notNearEnd && (roulette != NULL && roulette->baseDist < ENDDIST)) + return false; + if (K_DenyShieldOdds(item)) + return false; + + if (roulette && roulette->autoroulette == true) + { + if (K_DenyAutoRouletteOdds(item)) + { + return false; + } + } + + return true; +} + /*-------------------------------------------------- void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox) @@ -1382,7 +1651,9 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // actually calculate actual item reels. roulette->preexpdist = K_GetItemRouletteDistance(player, roulette->playing); roulette->dist = roulette->preexpdist; - roulette->dist = FixedMul(roulette->preexpdist, max(player->exp, FRACUNIT/2)); + + if (gametyperules & GTR_CIRCUIT) + roulette->dist = FixedMul(roulette->preexpdist, max(player->exp, FRACUNIT/2)); // == EVERYTHING FUCKED BELOW THIS LINE @@ -1391,148 +1662,63 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet UINT32 deltas[NUMKARTRESULTS]; // how different is that strength from target? UINT32 candidates[NUMKARTRESULTS]; // how many of this item should we try to insert? UINT32 dupetolerance[NUMKARTRESULTS]; // how willing are we to select this item after already selecting it? higher values = lower dupe penalty - UINT32 duplicates[NUMKARTRESULTS]; // how many copies of this item are already waiting to be inserted? boolean permit[NUMKARTRESULTS]; // is this item allowed? - boolean firstonly[NUMKARTRESULTS]; // is this item only first? - boolean nofirst[NUMKARTRESULTS]; // is this item never for first? - // Items permissible? + boolean rival = (player->bot && (player->botvars.rival || cv_levelskull.value)); + boolean mothfilter = true; // strip unusually weak items from reel? + UINT8 reelsize = 15; + UINT32 humanscaler = 200; + + // Cache which items are permissible for (i = 1; i < NUMKARTRESULTS; i++) { - permit[i] = true; + permit[i] = K_ShouldAllowItem(i, roulette); - boolean notNearEnd = false; - boolean powerItem = false; - boolean cooldownOnStart = false; - - switch (i) - { - case KITEM_BANANA: - case KITEM_EGGMAN: - case KITEM_SUPERRING: - { - notNearEnd = true; - break; - } + // CONS_Printf("%s permit prepass %d\n", cv_items[i-1].name, permit[i]); - case KITEM_ROCKETSNEAKER: - case KITEM_JAWZ: - case KITEM_LANDMINE: - case KITEM_DROPTARGET: - case KITEM_BALLHOG: - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEORBINAUT: - case KRITEM_QUADORBINAUT: - case KRITEM_DUALJAWZ: - { - powerItem = true; - break; - } + if (permit[i]) + permit[i] = K_ShouldPlayerAllowItem(i, player); - case KITEM_HYUDORO: - case KRITEM_TRIPLEBANANA: - { - powerItem = true; - notNearEnd = true; - break; - } - - case KITEM_INVINCIBILITY: - case KITEM_MINE: - case KITEM_GROW: - case KITEM_BUBBLESHIELD: - { - cooldownOnStart = true; - powerItem = true; - break; - } - - case KITEM_FLAMESHIELD: - case KITEM_GARDENTOP: - { - cooldownOnStart = true; - powerItem = true; - notNearEnd = true; - break; - } - - case KITEM_SPB: - { - cooldownOnStart = true; - notNearEnd = true; - // TODO forcing, just disable for now - permit[i] = false; - break; - } - - case KITEM_SHRINK: - { - cooldownOnStart = true; - powerItem = true; - notNearEnd = true; - break; - } - - case KITEM_LIGHTNINGSHIELD: - { - cooldownOnStart = true; - powerItem = true; - if ((gametyperules & GTR_CIRCUIT) && spbplace != -1) - { - permit[i] = false; - } - break; - } - } - - if (cooldownOnStart && (leveltime < (30*TICRATE) + starttime)) - permit[i] = false; - if (notNearEnd && (roulette != NULL && roulette->baseDist < ENDDIST)) - permit[i] = false; - + // CONS_Printf("%s permit postpass %d\n", cv_items[i-1].name, permit[i]); } - - // invent some bullshit Ps based on existing useodds, temp + + // temp - i have no fucking clue how pointers work i am so sorry for (i = 1; i < NUMKARTRESULTS; i++) { - powers[i] = 0; - dupetolerance[i] = 0; - UINT32 entries = 0; - firstonly[i] = true; - nofirst[i] = true; - for (j = 0; j < 8; j++) + if (gametyperules & GTR_BUMPERS) { - if (K_KartItemOddsRace[i-1][j] > 0) - { - powers[i] += (j+1) * DISTVAR * K_KartItemOddsRace[i-1][j]; - entries += K_KartItemOddsRace[i-1][j]; - if (j > 0) - firstonly[i] = false; - if (j == 0) - nofirst[i] = false; - } + powers[i] = humanscaler * K_DynamicItemOddsBattle[i-1][0]; + dupetolerance[i] = K_DynamicItemOddsBattle[i-1][1]; + mothfilter = false; + } + else if (specialstageinfo.valid == true) + { + powers[i] = humanscaler * K_DynamicItemOddsSpecial[i-1][0]; + dupetolerance[i] = K_DynamicItemOddsSpecial[i-1][1]; + reelsize = 8; + mothfilter = false; + } + else + { + powers[i] = humanscaler * K_DynamicItemOddsRace[i-1][0]; + dupetolerance[i] = K_DynamicItemOddsRace[i-1][1]; } - dupetolerance[i] += entries; - - if (entries) - powers[i] /= entries; - - // CONS_Printf("%s: %d - %d - FO %d - NF %d\n", cv_items[i-1].name, powers[i], dupetolerance[i], firstonly[i], nofirst[i]); + if (K_IsItemPower(i) && rival) + powers[i] = 3 * powers[i] / 4; + if (K_IsItemPower(i) && franticitems) + powers[i] = 3 * powers[i] / 4; } - // temp - null stuff that doesn't have odds, then distance correct + // null stuff that doesn't have odds for (i = 1; i < NUMKARTRESULTS; i++) { if (powers[i] == 0) + { + // CONS_Printf("%s nulled\n", cv_items[i-1].name); permit[i] = false; - else - powers[i] -= DISTVAR; - } - - // stupid hack for test run / no waypoints - if (player->position == 1) - targetpower = 0; + } + } // Starting deltas for (i = 1; i < NUMKARTRESULTS; i++) @@ -1542,11 +1728,14 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // CONS_Printf("starting delta for %s is %d\n", cv_items[i-1].name, deltas[i]); } + UINT8 added = 0; + UINT32 totalreelpower = 0; + // let's start finding items to list - for (i = 0; i < 15; i++) + for (i = 0; i < reelsize; i++) { UINT32 lowestdelta = INT32_MAX; - size_t lowestindex = 0; + size_t bestitem = 0; // CONS_Printf("LOOP %d\n", i); @@ -1555,7 +1744,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // (but ignore items that aren't allowed now) for (j = 1; j < NUMKARTRESULTS; j++) { - // CONS_Printf("precheck %s, FO %d NF %d CD %d\n", cv_items[j-1].name, firstonly[j], nofirst[j], K_GetItemCooldown(j)); + // CONS_Printf("precheck %s, perm %d CD %d\n", cv_items[j-1].name, permit[j], K_GetItemCooldown(j)); if (!permit[j]) continue; @@ -1563,40 +1752,97 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet continue; if (!K_ItemEnabled(j)) continue; - if (firstonly[j] && player->position > 1) - continue; - if (nofirst[j] && player->position == 1) - continue; // CONS_Printf("checking %s, delta %d\n", cv_items[j-1].name, deltas[j]); if (lowestdelta > deltas[j]) { - lowestindex = j; + bestitem = j; lowestdelta = deltas[j]; } - } // couldn't find an item? goodbye lol - if (lowestindex == 0) + if (bestitem == 0) break; + // UINT32 deltapenalty = (DISTVAR*4)^(candidates[bestitem])/dupetolerance[bestitem]; + UINT32 deltapenalty = 4*DISTVAR*(1+candidates[bestitem])/dupetolerance[bestitem]; + + if (K_IsItemPower(i) && rival) + deltapenalty = 3 * deltapenalty / 4; + if (K_IsItemPower(i) && franticitems) + deltapenalty = 3 * deltapenalty / 4; + + if (cv_kartdebugdistribution.value > 1) + { + UINT16 BASE_X = 18; + UINT16 BASE_Y = 5+12*i; + INT32 FLAGS = V_SNAPTOTOP|V_SNAPTOLEFT; + // V_DrawThinString(BASE_X + 100, BASE_Y, FLAGS, va("%s", cv_items[lowestindex-1].name)); + V_DrawThinString(BASE_X + 35, BASE_Y, FLAGS, va("P%d", powers[bestitem]/humanscaler)); + V_DrawThinString(BASE_X + 65, BASE_Y, FLAGS, va("D%d", deltas[bestitem]/humanscaler)); + V_DrawThinString(BASE_X + 20, BASE_Y, FLAGS, va("%d", dupetolerance[bestitem])); + //V_DrawThinString(BASE_X + 70, BASE_Y, FLAGS, va("+%d", deltapenalty)); + V_DrawFixedPatch(BASE_X*FRACUNIT, (BASE_Y-7)*FRACUNIT, (FRACUNIT >> 1), FLAGS, K_GetSmallStaticCachedItemPatch(bestitem), NULL); + UINT8 amount = K_ItemResultToAmount(bestitem); + if (amount > 1) + { + V_DrawThinString(BASE_X, BASE_Y, FLAGS, va("x%d", amount)); + } + } + // otherwise, prep it to be added and give it a duplicaton penalty, // so that a different item is more likely to be inserted next - candidates[lowestindex]++; - duplicates[lowestindex]++; - deltas[lowestindex] += (DISTVAR*6/dupetolerance[lowestindex])^(duplicates[lowestindex]); + candidates[bestitem]++; + deltas[bestitem] += deltapenalty; + + totalreelpower += powers[bestitem]; + added++; // CONS_Printf("added %s with candidates %d\n", cv_items[lowestindex-1].name, candidates[lowestindex]); } + UINT8 debugcount = 0; + UINT32 meanreelpower = totalreelpower/max(added, 1); + // set up the list indices used to random-shuffle the ro ulette for (i = 1; i < NUMKARTRESULTS; i++) - { - spawnChance[i] = ( - totalSpawnChance += candidates[i] - ); + { + // filter items vastly too weak for this reel + boolean reject = (powers[i] + DISTVAR < meanreelpower); + + if (!mothfilter) + reject = false; + + if (cv_kartdebugdistribution.value && candidates[i]) + { + UINT16 BASE_X = 280; + UINT16 BASE_Y = 5+12*debugcount; + INT32 FLAGS = V_SNAPTOTOP|V_SNAPTORIGHT; + + V_DrawThinString(BASE_X - 12, 5, FLAGS, va("%d", targetpower/humanscaler)); + + for(UINT8 k = 0; k < candidates[i]; k++) + V_DrawFixedPatch((BASE_X + 3*k)*FRACUNIT, (BASE_Y-7)*FRACUNIT, (FRACUNIT >> 1), FLAGS, K_GetSmallStaticCachedItemPatch(i), NULL); + + UINT8 amount = K_ItemResultToAmount(i); + if (amount > 1) + { + V_DrawThinString(BASE_X, BASE_Y, FLAGS, va("x%d", amount)); + } + + if (reject) + V_DrawThinString(BASE_X, BASE_Y, FLAGS|V_60TRANS, va("WEAK")); + debugcount++; + } + + if (!reject) + { + spawnChance[i] = ( + totalSpawnChance += candidates[i] + ); + } } if (totalSpawnChance == 0) @@ -1607,8 +1853,10 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet return; } - // Create the same item reel given the same inputs. - // P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); + /* + if (cv_kartdebugdistribution.value) + P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); + */ // and insert all of our candidates into the roulette in a random order while (totalSpawnChance > 0) From d6210023d83f87da6f098eb9b54e44d431b70391 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 20 Aug 2024 18:00:16 -0700 Subject: [PATCH 19/38] Don't directly scale item power when frantic (distance is already scaled!) --- src/k_roulette.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 9959b6719..37d800dd4 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1703,11 +1703,6 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet powers[i] = humanscaler * K_DynamicItemOddsRace[i-1][0]; dupetolerance[i] = K_DynamicItemOddsRace[i-1][1]; } - - if (K_IsItemPower(i) && rival) - powers[i] = 3 * powers[i] / 4; - if (K_IsItemPower(i) && franticitems) - powers[i] = 3 * powers[i] / 4; } // null stuff that doesn't have odds From 4fc9bf95cfd1cac7aca96e961a353e8a03cb8f60 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 20 Aug 2024 21:38:44 -0700 Subject: [PATCH 20/38] Item table refinement --- src/k_roulette.c | 53 ++++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 37d800dd4..f56c2f833 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1313,6 +1313,31 @@ static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST + FixedMul(ROULETTE_SPEED_SLOWEST - ROULETTE_SPEED_FASTEST, total); } +static boolean K_IsItemPower(kartitems_t item) +{ + switch (item) + { + case KITEM_ROCKETSNEAKER: + case KITEM_JAWZ: + case KITEM_LANDMINE: + case KITEM_DROPTARGET: + case KITEM_BALLHOG: + case KRITEM_TRIPLESNEAKER: + case KRITEM_TRIPLEORBINAUT: + case KRITEM_QUADORBINAUT: + case KRITEM_DUALJAWZ: + case KITEM_HYUDORO: + case KRITEM_TRIPLEBANANA: + case KITEM_FLAMESHIELD: + case KITEM_GARDENTOP: + case KITEM_SHRINK: + case KITEM_LIGHTNINGSHIELD: + return true; + default: + return false; + } +} + static boolean K_IsItemFirstOnly(kartitems_t item) { switch (item) @@ -1355,31 +1380,10 @@ static boolean K_ShouldPlayerAllowItem(kartitems_t item, const player_t *player) if (player->position == 1) return K_IsItemFirstPermitted(item); else - return !K_IsItemFirstOnly(item); -} - -static boolean K_IsItemPower(kartitems_t item) -{ - switch (item) { - case KITEM_ROCKETSNEAKER: - case KITEM_JAWZ: - case KITEM_LANDMINE: - case KITEM_DROPTARGET: - case KITEM_BALLHOG: - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEORBINAUT: - case KRITEM_QUADORBINAUT: - case KRITEM_DUALJAWZ: - case KITEM_HYUDORO: - case KRITEM_TRIPLEBANANA: - case KITEM_FLAMESHIELD: - case KITEM_GARDENTOP: - case KITEM_SHRINK: - case KITEM_LIGHTNINGSHIELD: - return true; - default: + if (K_IsItemPower(item) && (leveltime < ((15*TICRATE) + starttime))) return false; + return !K_IsItemFirstOnly(item); } } @@ -1458,7 +1462,7 @@ static boolean K_ShouldAllowItem(kartitems_t item, const itemroulette_t *roulett break; } - if (cooldownOnStart && (leveltime < (30*TICRATE) + starttime)) + if (cooldownOnStart && (leveltime < ((30*TICRATE) + starttime))) return false; if (notNearEnd && (roulette != NULL && roulette->baseDist < ENDDIST)) return false; @@ -1700,6 +1704,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } else { + humanscaler = 250; powers[i] = humanscaler * K_DynamicItemOddsRace[i-1][0]; dupetolerance[i] = K_DynamicItemOddsRace[i-1][1]; } From b3abb8ca37b7021fa1de54e715af2d874afa814b Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 20 Aug 2024 22:53:38 -0700 Subject: [PATCH 21/38] Stretch items further in large games --- src/k_roulette.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index f56c2f833..4c66b3291 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1671,7 +1671,15 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet boolean rival = (player->bot && (player->botvars.rival || cv_levelskull.value)); boolean mothfilter = true; // strip unusually weak items from reel? UINT8 reelsize = 15; - UINT32 humanscaler = 200; + UINT32 humanscaler = 250; + + for (i = 0; i < MAXPLAYERS; ++i) + { + if (D_IsPlayerHumanAndGaming(i)) + { + humanscaler += 15; + } + } // Cache which items are permissible for (i = 1; i < NUMKARTRESULTS; i++) @@ -1704,7 +1712,6 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } else { - humanscaler = 250; powers[i] = humanscaler * K_DynamicItemOddsRace[i-1][0]; dupetolerance[i] = K_DynamicItemOddsRace[i-1][1]; } From 9e90cb80dddff777f79f62b8f03050fc8979d495 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 20 Aug 2024 23:28:27 -0700 Subject: [PATCH 22/38] Restore some Battle stuff --- src/k_kart.c | 28 +++- src/k_roulette.c | 346 +++-------------------------------------------- src/k_roulette.h | 11 +- 3 files changed, 48 insertions(+), 337 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 6e2bd674b..d16b3604c 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -7413,8 +7413,32 @@ void K_DropHnextList(player_t *player) SINT8 K_GetTotallyRandomResult(UINT8 useodds) { - // TODO - return 0; + INT32 spawnchance[NUMKARTRESULTS]; + INT32 totalspawnchance = 0; + INT32 i; + + memset(spawnchance, 0, sizeof (spawnchance)); + + for (i = 1; i < NUMKARTRESULTS; i++) + { + // Avoid calling K_FillItemRouletteData since that + // function resets PR_ITEM_ROULETTE. + spawnchance[i] = ( + totalspawnchance += K_KartGetBattleOdds(NULL, useodds, i) + ); + } + + if (totalspawnchance > 0) + { + totalspawnchance = P_RandomKey(PR_ITEM_ROULETTE, totalspawnchance); + for (i = 0; i < NUMKARTRESULTS && spawnchance[i] <= totalspawnchance; i++); + } + else + { + i = KITEM_SAD; + } + + return i; } mobj_t *K_CreatePaperItem(fixed_t x, fixed_t y, fixed_t z, angle_t angle, SINT8 flip, UINT8 type, UINT16 amount) diff --git a/src/k_roulette.c b/src/k_roulette.c index 4c66b3291..5d67884c4 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -181,41 +181,8 @@ static UINT32 K_DynamicItemOddsSpecial[NUMKARTRESULTS-1][2] = {0, 0}, // triplegachabom }; -static UINT8 K_KartItemOddsRace[NUMKARTRESULTS-1][8] = -{ - { 0, 0, 2, 3, 4, 0, 0, 0 }, // Sneaker - { 0, 0, 0, 0, 0, 3, 4, 5 }, // Rocket Sneaker - { 0, 0, 0, 0, 2, 5, 5, 7 }, // Invincibility - { 2, 3, 1, 0, 0, 0, 0, 0 }, // Banana - { 1, 2, 0, 0, 0, 0, 0, 0 }, // Eggman Monitor - { 5, 5, 2, 2, 0, 0, 0, 0 }, // Orbinaut - { 0, 4, 2, 1, 0, 0, 0, 0 }, // Jawz - { 0, 3, 3, 2, 0, 0, 0, 0 }, // Mine - { 3, 0, 0, 0, 0, 0, 0, 0 }, // Land Mine - { 0, 0, 2, 2, 0, 0, 0, 0 }, // Ballhog - { 0, 0, 0, 0, 0, 2, 4, 0 }, // Self-Propelled Bomb - { 0, 0, 0, 0, 2, 5, 0, 0 }, // Grow - { 0, 0, 0, 0, 0, 2, 4, 2 }, // Shrink - { 1, 0, 0, 0, 0, 0, 0, 0 }, // Lightning Shield - { 0, 1, 2, 1, 0, 0, 0, 0 }, // Bubble Shield - { 0, 0, 0, 0, 0, 1, 3, 5 }, // Flame Shield - { 3, 0, 0, 0, 0, 0, 0, 0 }, // Hyudoro - { 0, 0, 0, 0, 0, 0, 0, 0 }, // Pogo Spring - { 2, 1, 1, 0, 0, 0, 0, 0 }, // Super Ring - { 0, 0, 0, 0, 0, 0, 0, 0 }, // Kitchen Sink - { 3, 0, 0, 0, 0, 0, 0, 0 }, // Drop Target - { 0, 0, 0, 1, 2, 2, 0, 0 }, // Garden Top - { 0, 0, 0, 0, 0, 0, 0, 0 }, // Gachabom - { 0, 0, 2, 3, 3, 1, 0, 0 }, // Sneaker x2 - { 0, 0, 0, 0, 4, 4, 4, 0 }, // Sneaker x3 - { 0, 1, 1, 0, 0, 0, 0, 0 }, // Banana x3 - { 0, 0, 1, 0, 0, 0, 0, 0 }, // Orbinaut x3 - { 0, 0, 0, 2, 0, 0, 0, 0 }, // Orbinaut x4 - { 0, 0, 1, 2, 1, 0, 0, 0 }, // Jawz x2 - { 0, 0, 0, 0, 0, 0, 0, 0 } // Gachabom x3 -}; -static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] = +static UINT8 K_KartLegacyBattleOdds[NUMKARTRESULTS-1][2] = { { 0, 1 }, // Sneaker { 0, 0 }, // Rocket Sneaker @@ -249,40 +216,6 @@ static UINT8 K_KartItemOddsBattle[NUMKARTRESULTS-1][2] = { 2, 0 } // Gachabom x3 }; -static UINT8 K_KartItemOddsSpecial[NUMKARTRESULTS-1][4] = -{ - { 1, 1, 0, 0 }, // Sneaker - { 0, 0, 0, 0 }, // Rocket Sneaker - { 0, 0, 0, 0 }, // Invincibility - { 0, 0, 0, 0 }, // Banana - { 0, 0, 0, 0 }, // Eggman Monitor - { 1, 1, 1, 0 }, // Orbinaut - { 1, 1, 0, 0 }, // Jawz - { 0, 0, 0, 0 }, // Mine - { 0, 0, 0, 0 }, // Land Mine - { 0, 0, 0, 0 }, // Ballhog - { 0, 0, 0, 1 }, // Self-Propelled Bomb - { 0, 0, 0, 0 }, // Grow - { 0, 0, 0, 0 }, // Shrink - { 0, 0, 0, 0 }, // Lightning Shield - { 0, 0, 0, 0 }, // Bubble Shield - { 0, 0, 0, 0 }, // Flame Shield - { 0, 0, 0, 0 }, // Hyudoro - { 0, 0, 0, 0 }, // Pogo Spring - { 0, 0, 0, 0 }, // Super Ring - { 0, 0, 0, 0 }, // Kitchen Sink - { 0, 0, 0, 0 }, // Drop Target - { 0, 0, 0, 0 }, // Garden Top - { 0, 0, 0, 0 }, // Gachabom - { 0, 0, 1, 1 }, // Sneaker x2 - { 0, 0, 0, 0 }, // Sneaker x3 - { 0, 0, 0, 0 }, // Banana x3 - { 0, 0, 1, 1 }, // Orbinaut x3 - { 0, 0, 0, 0 }, // Orbinaut x4 - { 0, 0, 1, 1 }, // Jawz x2 - { 0, 0, 0, 0 } // Gachabom x3 -}; - static kartitems_t K_KartItemReelSpecialEnd[] = { KITEM_SUPERRING, @@ -688,9 +621,9 @@ static boolean K_DenyAutoRouletteOdds(kartitems_t item) } /*-------------------------------------------------- - static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position) + static fixed_t K_PercentSPBOdds(const itemroulette_t *roulette, UINT8 position) - Adjust odds of SPB according to distances of first and + Provide odds of SPB according to distances of first and second place players. Input Arguments:- @@ -702,7 +635,7 @@ static boolean K_DenyAutoRouletteOdds(kartitems_t item) Return:- New item odds. --------------------------------------------------*/ -static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position) +static fixed_t K_PercentSPBOdds(const itemroulette_t *roulette, UINT8 position) { I_Assert(roulette != NULL); @@ -728,273 +661,29 @@ static fixed_t K_AdjustSPBOdds(const itemroulette_t *roulette, UINT8 position) multiplier = FRACUNIT; } - return FixedMul(maxOdds, multiplier); + return multiplier; } } -typedef struct { - boolean powerItem; - boolean cooldownOnStart; - boolean notNearEnd; - - // gameplay state - boolean rival; // player is a bot Rival -} itemconditions_t; /*-------------------------------------------------- - static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette) - - Adjust item odds to certain group conditions. - - Input Arguments:- - newOdds - The item odds to adjust. - conditions - The conditions state. - roulette - The roulette data that we intend to - insert this item into. - - Return:- - New item odds. ---------------------------------------------------*/ -static fixed_t K_AdjustItemOddsToConditions(fixed_t newOdds, const itemconditions_t *conditions, const itemroulette_t *roulette) -{ - // TODO - return newOdds; - - // None if this applies outside of Race modes (for now?) - if ((gametyperules & GTR_CIRCUIT) == 0) - { - return newOdds; - } - - if ((conditions->cooldownOnStart == true) && (leveltime < (30*TICRATE) + starttime)) - { - // This item should not appear at the beginning of a race. (Usually really powerful crowd-breaking items) - newOdds = 0; - } - else if ((conditions->notNearEnd == true) && (roulette != NULL && roulette->baseDist < ENDDIST)) - { - // This item should not appear at the end of a race. (Usually trap items that lose their effectiveness) - newOdds = 0; - } - else if (conditions->powerItem == true) - { - // This item is a "power item". This activates "frantic item" toggle related functionality. - if (franticitems == true) - { - // First, power items multiply their odds by 2 if frantic items are on; easy-peasy. - newOdds *= 2; - } - - if (conditions->rival == true) - { - // The Rival bot gets frantic-like items, also :p - newOdds *= 2; - } - - if (roulette != NULL) - { - newOdds = FixedMul(newOdds, FRACUNIT + K_ItemOddsScale(roulette->playing)); - } - } - - return newOdds; -} - -/*-------------------------------------------------- - INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) + INT32 K_KartGetBattleOdds(const player_t *player, UINT8 pos, kartitems_t item) See header file for description. --------------------------------------------------*/ -INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item) +INT32 K_KartGetBattleOdds(const player_t *player, UINT8 pos, kartitems_t item) { - boolean bot = false; - UINT8 position = 0; - - itemconditions_t conditions = { - .powerItem = false, - .cooldownOnStart = false, - .notNearEnd = false, - .rival = false, - }; - fixed_t newOdds = 0; I_Assert(item > KITEM_NONE); // too many off by one scenarioes. I_Assert(item < NUMKARTRESULTS); - if (player != NULL) - { - bot = player->bot; - conditions.rival = (bot == true && (player->botvars.rival || cv_levelskull.value)); - position = player->position; - } - - if (K_ItemEnabled(item) == false) - { - return 0; - } - - if (K_GetItemCooldown(item) > 0) - { - // Cooldown is still running, don't give another. - return 0; - } - - /* - if (bot) - { - // TODO: Item use on bots should all be passed-in functions. - // Instead of manually inserting these, it should return 0 - // for any items without an item use function supplied - - switch (item) - { - case KITEM_SNEAKER: - break; - default: - return 0; - } - } - */ - (void)bot; - - if (K_DenyShieldOdds(item)) - { - return 0; - } - - if (roulette && roulette->autoroulette == true) - { - if (K_DenyAutoRouletteOdds(item)) - { - return 0; - } - } - - if (gametype == GT_BATTLE) - { - I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table - newOdds = K_KartItemOddsBattle[item-1][pos]; - } - else if (specialstageinfo.valid == true) - { - I_Assert(pos < 4); // Ditto - newOdds = K_KartItemOddsSpecial[item-1][pos]; - } - else - { - I_Assert(pos < 8); // Ditto - newOdds = K_KartItemOddsRace[item-1][pos]; - } + I_Assert(pos < 2); // DO NOT allow positions past the bounds of the table + newOdds = K_KartLegacyBattleOdds[item-1][pos]; newOdds <<= FRACBITS; - switch (item) - { - case KITEM_BANANA: - case KITEM_EGGMAN: - case KITEM_SUPERRING: - { - conditions.notNearEnd = true; - break; - } - - case KITEM_ROCKETSNEAKER: - case KITEM_JAWZ: - case KITEM_LANDMINE: - case KITEM_DROPTARGET: - case KITEM_BALLHOG: - case KRITEM_TRIPLESNEAKER: - case KRITEM_TRIPLEORBINAUT: - case KRITEM_QUADORBINAUT: - case KRITEM_DUALJAWZ: - { - conditions.powerItem = true; - break; - } - - case KITEM_HYUDORO: - case KRITEM_TRIPLEBANANA: - { - conditions.powerItem = true; - conditions.notNearEnd = true; - break; - } - - case KITEM_INVINCIBILITY: - case KITEM_MINE: - case KITEM_GROW: - case KITEM_BUBBLESHIELD: - { - conditions.cooldownOnStart = true; - conditions.powerItem = true; - break; - } - - case KITEM_FLAMESHIELD: - case KITEM_GARDENTOP: - { - conditions.cooldownOnStart = true; - conditions.powerItem = true; - conditions.notNearEnd = true; - break; - } - - case KITEM_SPB: - { - conditions.cooldownOnStart = true; - conditions.notNearEnd = true; - - if (roulette != NULL && - (gametyperules & GTR_CIRCUIT) && - specialstageinfo.valid == false) - { - newOdds = K_AdjustSPBOdds(roulette, position); - } - break; - } - - case KITEM_SHRINK: - { - conditions.cooldownOnStart = true; - conditions.powerItem = true; - conditions.notNearEnd = true; - - if (roulette != NULL && - (gametyperules & GTR_CIRCUIT) && - roulette->playing - 1 <= roulette->exiting) - { - return 0; - } - break; - } - - case KITEM_LIGHTNINGSHIELD: - { - conditions.cooldownOnStart = true; - conditions.powerItem = true; - - if ((gametyperules & GTR_CIRCUIT) && spbplace != -1) - { - return 0; - } - break; - } - - default: - { - break; - } - } - - if (newOdds == 0) - { - // Nothing else we want to do with odds matters at this point :p - return newOdds; - } - - newOdds = FixedInt(FixedRound(K_AdjustItemOddsToConditions(newOdds, &conditions, roulette))); return newOdds; } @@ -1659,7 +1348,12 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet if (gametyperules & GTR_CIRCUIT) roulette->dist = FixedMul(roulette->preexpdist, max(player->exp, FRACUNIT/2)); - // == EVERYTHING FUCKED BELOW THIS LINE + // Dynamic Roulette. Oh boy! + + // STAGE 1: Determine what items are permissible + // STAGE 2: Determine the item that's most appropriate for our distance from leader + // STAGE 3: Pick that item, then penalize it + // STAGE 4: Repeat 3 until the reel is full, then cram everything in UINT32 targetpower = roulette->dist; // fill roulette with items around this value! UINT32 powers[NUMKARTRESULTS]; // how strong is each item? @@ -1670,8 +1364,8 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet boolean rival = (player->bot && (player->botvars.rival || cv_levelskull.value)); boolean mothfilter = true; // strip unusually weak items from reel? - UINT8 reelsize = 15; - UINT32 humanscaler = 250; + UINT8 reelsize = 15; // How many items to attempt to add in prepass? + UINT32 humanscaler = 250; // Scaler that converts "useodds" style distances in odds tables to raw distances. for (i = 0; i < MAXPLAYERS; ++i) { @@ -1697,6 +1391,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // temp - i have no fucking clue how pointers work i am so sorry for (i = 1; i < NUMKARTRESULTS; i++) { + // NOTE: Battle odds are underspecified, we don't invoke roulettes in this mode! if (gametyperules & GTR_BUMPERS) { powers[i] = humanscaler * K_DynamicItemOddsBattle[i-1][0]; @@ -1860,11 +1555,6 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet return; } - /* - if (cv_kartdebugdistribution.value) - P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); - */ - // and insert all of our candidates into the roulette in a random order while (totalSpawnChance > 0) { diff --git a/src/k_roulette.h b/src/k_roulette.h index 058562cb6..f05354e93 100644 --- a/src/k_roulette.h +++ b/src/k_roulette.h @@ -78,17 +78,14 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result); /*-------------------------------------------------- - INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); + INT32 K_KartGetBattleOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); - Gets the frequency an item should show up in - an item bracket, and adjusted for special - factors (such as Frantic Items). + Gets legacy item priority. + Currently used only for Battle monitors/spawners. Input Arguments:- player - The player we intend to give the item to later. Can be NULL for generic use. - roulette - The roulette data that we intend to - insert this item into. pos - The item bracket we are in. item - The item to give. @@ -97,7 +94,7 @@ botItemPriority_e K_GetBotItemPriority(kartitems_t result); into the roulette. --------------------------------------------------*/ -INT32 K_KartGetItemOdds(const player_t *player, itemroulette_t *const roulette, UINT8 pos, kartitems_t item); +INT32 K_KartGetBattleOdds(const player_t *player, UINT8 pos, kartitems_t item); /*-------------------------------------------------- From e79eeec89665afa639f505bca842f54686f0ba2b Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Tue, 20 Aug 2024 23:59:03 -0700 Subject: [PATCH 23/38] Dynamic Roulette: SPB --- src/k_roulette.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 5d67884c4..15bd10ad8 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -52,6 +52,7 @@ #include "k_grandprix.h" #include "k_specialstage.h" #include "k_hud.h" // distribution debugger +#include "m_easing.h" // Magic number distance for use with item roulette tiers #define DISTVAR (2048) @@ -648,7 +649,6 @@ static fixed_t K_PercentSPBOdds(const itemroulette_t *roulette, UINT8 position) { const UINT32 dist = max(0, ((signed)roulette->secondToFirst) - SPBSTARTDIST); const UINT32 distRange = SPBFORCEDIST - SPBSTARTDIST; - const fixed_t maxOdds = 20 << FRACBITS; fixed_t multiplier = FixedDiv(dist, distRange); if (multiplier < 0) @@ -661,6 +661,8 @@ static fixed_t K_PercentSPBOdds(const itemroulette_t *roulette, UINT8 position) multiplier = FRACUNIT; } + CONS_Printf("%d; %d / %d\n", leveltime, dist, roulette->secondToFirst); + return multiplier; } } @@ -1365,15 +1367,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet boolean rival = (player->bot && (player->botvars.rival || cv_levelskull.value)); boolean mothfilter = true; // strip unusually weak items from reel? UINT8 reelsize = 15; // How many items to attempt to add in prepass? - UINT32 humanscaler = 250; // Scaler that converts "useodds" style distances in odds tables to raw distances. - - for (i = 0; i < MAXPLAYERS; ++i) - { - if (D_IsPlayerHumanAndGaming(i)) - { - humanscaler += 15; - } - } + UINT32 humanscaler = 250 + (roulette->playing * 15); // Scaler that converts "useodds" style distances in odds tables to raw distances. // Cache which items are permissible for (i = 1; i < NUMKARTRESULTS; i++) @@ -1505,6 +1499,14 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // CONS_Printf("added %s with candidates %d\n", cv_items[lowestindex-1].name, candidates[lowestindex]); } + fixed_t spb_odds = K_PercentSPBOdds(roulette, player->position); + + if ((gametyperules & GTR_CIRCUIT) & (spb_odds > 0) & (spbplace == -1)) + { + permit[KITEM_SPB] = true; + deltas[KITEM_SPB] = Easing_Linear(spb_odds, 3000, 0); + } + UINT8 debugcount = 0; UINT32 meanreelpower = totalreelpower/max(added, 1); From 158052158e7adbf2216166d87222babd55b05fc6 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Wed, 21 Aug 2024 00:08:19 -0700 Subject: [PATCH 24/38] Dynamic Roulette: SPB safety --- src/k_roulette.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 15bd10ad8..020b184af 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1501,7 +1501,9 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet fixed_t spb_odds = K_PercentSPBOdds(roulette, player->position); - if ((gametyperules & GTR_CIRCUIT) & (spb_odds > 0) & (spbplace == -1)) + if ((gametyperules & GTR_CIRCUIT) + && specialstageinfo.valid == false + && (spb_odds > 0) & (spbplace == -1)) { permit[KITEM_SPB] = true; deltas[KITEM_SPB] = Easing_Linear(spb_odds, 3000, 0); From d8fc1fed649d9d27fb58d074bf4161852441a9ed Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Wed, 21 Aug 2024 00:41:35 -0700 Subject: [PATCH 25/38] Item "loneliness" --- src/k_roulette.c | 78 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 020b184af..2f378ad75 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -661,8 +661,6 @@ static fixed_t K_PercentSPBOdds(const itemroulette_t *roulette, UINT8 position) multiplier = FRACUNIT; } - CONS_Printf("%d; %d / %d\n", leveltime, dist, roulette->secondToFirst); - return multiplier; } } @@ -839,6 +837,8 @@ static void K_InitRoulette(itemroulette_t *const roulette) roulette->firstDist = K_UndoMapScaling(K_GetSpecialUFODistance()); } + + // Calculate 2nd's distance from 1st, for SPB if (roulette->firstDist != UINT32_MAX && roulette->secondDist != UINT32_MAX && roulette->secondDist > roulette->firstDist) @@ -1060,6 +1060,40 @@ static boolean K_IsItemFirstPermitted(kartitems_t item) } } + +static boolean K_IsItemSpeed(kartitems_t item) +{ + switch (item) + { + case KITEM_SNEAKER: + case KRITEM_DUALSNEAKER: + case KRITEM_TRIPLESNEAKER: + case KITEM_FLAMESHIELD: + case KITEM_ROCKETSNEAKER: + return true; + default: + return false; + } +} + +static boolean K_IsItemUselessAlone(kartitems_t item) +{ + switch (item) + { + case KITEM_JAWZ: + case KRITEM_DUALJAWZ: + case KITEM_LIGHTNINGSHIELD: + case KITEM_ORBINAUT: + case KRITEM_TRIPLEORBINAUT: + case KRITEM_QUADORBINAUT: + case KITEM_BALLHOG: + case KITEM_BUBBLESHIELD: + return true; + default: + return false; + } +} + // Which items are disallowed for THIS player? static boolean K_ShouldPlayerAllowItem(kartitems_t item, const player_t *player) { @@ -1424,10 +1458,35 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // CONS_Printf("starting delta for %s is %d\n", cv_items[i-1].name, deltas[i]); } + // A lot of items suck if no players are nearby to interact with them. + // Should we bias towards items that get us back to the action? + UINT32 lonelinessThreshold = 3*DISTVAR/2; + UINT32 toAttacker = lonelinessThreshold; + UINT32 toDefender = lonelinessThreshold; + boolean lonely = false; + + if ((gametyperules & GTR_CIRCUIT) && specialstageinfo.valid == false) + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true || players[i].exiting) + continue; + + if (players[i].position == player->position + 1) + toAttacker = K_UndoMapScaling(players[i].distancetofinish - player->distancetofinish); + + if (players[i].position == player->position - 1) + toDefender = K_UndoMapScaling(player->distancetofinish - players[i].distancetofinish); + } + } + + if (toAttacker >= lonelinessThreshold && toDefender >= lonelinessThreshold && player->position > 1) + lonely = true; + + // let's start finding items to list UINT8 added = 0; UINT32 totalreelpower = 0; - // let's start finding items to list for (i = 0; i < reelsize; i++) { UINT32 lowestdelta = INT32_MAX; @@ -1465,10 +1524,12 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // UINT32 deltapenalty = (DISTVAR*4)^(candidates[bestitem])/dupetolerance[bestitem]; UINT32 deltapenalty = 4*DISTVAR*(1+candidates[bestitem])/dupetolerance[bestitem]; - if (K_IsItemPower(i) && rival) + if (K_IsItemPower(bestitem) && rival) deltapenalty = 3 * deltapenalty / 4; - if (K_IsItemPower(i) && franticitems) + if (K_IsItemPower(bestitem) && franticitems) deltapenalty = 3 * deltapenalty / 4; + if (lonely && K_IsItemUselessAlone(bestitem)) + deltapenalty *= 2; if (cv_kartdebugdistribution.value > 1) { @@ -1499,6 +1560,8 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // CONS_Printf("added %s with candidates %d\n", cv_items[lowestindex-1].name, candidates[lowestindex]); } + // Introduce SPB to race if there's a major frontrun breakway + fixed_t spb_odds = K_PercentSPBOdds(roulette, player->position); if ((gametyperules & GTR_CIRCUIT) @@ -1529,6 +1592,11 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet V_DrawThinString(BASE_X - 12, 5, FLAGS, va("%d", targetpower/humanscaler)); + V_DrawThinString(BASE_X - 12, 5+12, FLAGS, va("%d", toAttacker)); + V_DrawThinString(BASE_X - 12, 5+24, FLAGS, va("%d", toDefender)); + if (lonely) + V_DrawThinString(BASE_X - 12, 5+36, FLAGS, va(":(")); + for(UINT8 k = 0; k < candidates[i]; k++) V_DrawFixedPatch((BASE_X + 3*k)*FRACUNIT, (BASE_Y-7)*FRACUNIT, (FRACUNIT >> 1), FLAGS, K_GetSmallStaticCachedItemPatch(i), NULL); From 5d19bfcb919268b428ec96d87c3769c27e6b0024 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Wed, 21 Aug 2024 02:28:31 -0700 Subject: [PATCH 26/38] Dynamic Roulette: mega cleanup, mini fixes --- src/k_hud.cpp | 51 ++-------- src/k_roulette.c | 244 +++++++++++++++++++++++++---------------------- src/k_roulette.h | 5 +- 3 files changed, 142 insertions(+), 158 deletions(-) diff --git a/src/k_hud.cpp b/src/k_hud.cpp index b903e8114..980f2e064 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -5684,61 +5684,28 @@ static void K_drawDistributionDebugger(void) itemroulette_t rouletteData = {0}; const fixed_t scale = (FRACUNIT >> 1); - const fixed_t space = 24 * scale; const fixed_t pad = 9 * scale; fixed_t x = -pad; - fixed_t y = -pad; - size_t i; if (R_GetViewNumber() != 0) // only for p1 { return; } - K_FillItemRouletteData(stplyr, &rouletteData, false); + K_FillItemRouletteData(stplyr, &rouletteData, false, true); - return; + if (cv_kartdebugdistribution.value <= 1) + return; - for (i = 0; i < rouletteData.itemListLen; i++) - { - const kartitems_t item = static_cast(rouletteData.itemList[i]); - UINT8 amount = 1; + V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+10, V_SNAPTOTOP|V_SNAPTORIGHT, va("speed = %u", rouletteData.speed)); - if (y > (BASEVIDHEIGHT << FRACBITS) - space - pad) - { - x += space; - y = -pad; - } + V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+22, V_SNAPTOTOP|V_SNAPTORIGHT, va("baseDist = %u", rouletteData.baseDist)); + V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+30, V_SNAPTOTOP|V_SNAPTORIGHT, va("dist = %u", rouletteData.dist)); - V_DrawFixedPatch(x, y, scale, V_SNAPTOTOP, - K_GetSmallStaticCachedItemPatch(item), NULL); - - // Display amount for multi-items - amount = K_ItemResultToAmount(item); - if (amount > 1) - { - V_DrawStringScaled( - x + (18 * scale), - y + (23 * scale), - scale, FRACUNIT, FRACUNIT, - V_SNAPTOTOP, - NULL, HU_FONT, - va("x%d", amount) - ); - } - - y += space; - } - - V_DrawRightAlignedString(320 - (x >> FRACBITS), 10, V_SNAPTOTOP, va("speed = %u", rouletteData.speed)); - - V_DrawRightAlignedString(320 - (x >> FRACBITS), 22, V_SNAPTOTOP, va("baseDist = %u", rouletteData.baseDist)); - V_DrawRightAlignedString(320 - (x >> FRACBITS), 30, V_SNAPTOTOP, va("dist = %u", rouletteData.dist)); - - V_DrawRightAlignedString(320 - (x >> FRACBITS), 42, V_SNAPTOTOP, va("firstDist = %u", rouletteData.firstDist)); - V_DrawRightAlignedString(320 - (x >> FRACBITS), 50, V_SNAPTOTOP, va("secondDist = %u", rouletteData.secondDist)); - V_DrawRightAlignedString(320 - (x >> FRACBITS), 58, V_SNAPTOTOP, va("secondToFirst = %u", rouletteData.secondToFirst)); + V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+42, V_SNAPTOTOP|V_SNAPTORIGHT, va("firstDist = %u", rouletteData.firstDist)); + V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+50, V_SNAPTOTOP|V_SNAPTORIGHT, va("secondDist = %u", rouletteData.secondDist)); + V_DrawRightAlignedThinString(320-(x >> FRACBITS), 100+58, V_SNAPTOTOP|V_SNAPTORIGHT, va("secondToFirst = %u", rouletteData.secondToFirst)); #ifndef ITEM_LIST_SIZE Z_Free(rouletteData.itemList); diff --git a/src/k_roulette.c b/src/k_roulette.c index 2f378ad75..1f1ff3183 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1004,6 +1004,9 @@ static void K_CalculateRouletteSpeed(itemroulette_t *const roulette) roulette->tics = roulette->speed = ROULETTE_SPEED_FASTEST + FixedMul(ROULETTE_SPEED_SLOWEST - ROULETTE_SPEED_FASTEST, total); } +// Honestly, the "power item" class is kind of a vestigial concept, +// but we'll faithfully port it over since it's not hurting anything so far +// (and it's at least ostensibly a Rival balancing mechanism, wheee). static boolean K_IsItemPower(kartitems_t item) { switch (item) @@ -1060,7 +1063,8 @@ static boolean K_IsItemFirstPermitted(kartitems_t item) } } - +// Maybe for later... +#if 0 static boolean K_IsItemSpeed(kartitems_t item) { switch (item) @@ -1075,6 +1079,7 @@ static boolean K_IsItemSpeed(kartitems_t item) return false; } } +#endif static boolean K_IsItemUselessAlone(kartitems_t item) { @@ -1094,7 +1099,7 @@ static boolean K_IsItemUselessAlone(kartitems_t item) } } -// Which items are disallowed for THIS player? +// Which items are disallowed for this player's specific placement? static boolean K_ShouldPlayerAllowItem(kartitems_t item, const player_t *player) { if (!(gametyperules & GTR_CIRCUIT)) @@ -1106,14 +1111,15 @@ static boolean K_ShouldPlayerAllowItem(kartitems_t item, const player_t *player) return K_IsItemFirstPermitted(item); else { + // A little inelegant: filter the most chaotic items from courses with early sets and tight layouts. if (K_IsItemPower(item) && (leveltime < ((15*TICRATE) + starttime))) return false; return !K_IsItemFirstOnly(item); } } -// Which items are disallowed for ALL players? -static boolean K_ShouldAllowItem(kartitems_t item, const itemroulette_t *roulette) +// Which items are disallowed because it's the wrong time for them? +static boolean K_TimingPermitsItem(kartitems_t item, const itemroulette_t *roulette) { if (!(gametyperules & GTR_CIRCUIT)) return true; @@ -1159,10 +1165,9 @@ static boolean K_ShouldAllowItem(kartitems_t item, const itemroulette_t *roulett case KITEM_SPB: { - cooldownOnStart = true; - notNearEnd = true; - // TODO forcing, just disable for now - return false; + // In Race, we reintroduce and reenable this item to counter breakaway frontruns. + // No need to roll it if that's not the case. + return false; break; } @@ -1191,26 +1196,16 @@ static boolean K_ShouldAllowItem(kartitems_t item, const itemroulette_t *roulett return false; if (notNearEnd && (roulette != NULL && roulette->baseDist < ENDDIST)) return false; - if (K_DenyShieldOdds(item)) - return false; - - if (roulette && roulette->autoroulette == true) - { - if (K_DenyAutoRouletteOdds(item)) - { - return false; - } - } return true; } /*-------------------------------------------------- - void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox) + void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun) See header file for description. --------------------------------------------------*/ -void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox) +void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun) { UINT32 spawnChance[NUMKARTRESULTS] = {0}; UINT32 totalSpawnChance = 0; @@ -1384,39 +1379,53 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet if (gametyperules & GTR_CIRCUIT) roulette->dist = FixedMul(roulette->preexpdist, max(player->exp, FRACUNIT/2)); + // =============================================================================== // Dynamic Roulette. Oh boy! - - // STAGE 1: Determine what items are permissible - // STAGE 2: Determine the item that's most appropriate for our distance from leader - // STAGE 3: Pick that item, then penalize it - // STAGE 4: Repeat 3 until the reel is full, then cram everything in + // Alright, here's the broad plan: + // 1: Determine what items are permissible + // 2: Determine the permitted item that's most appropriate for our distance from leader + // 3: Pick that item, then penalize it so it's less likely to be repicked + // 4: Repeat 3 until we've picked enough stuff + // 5: Skim any items that are much weaker than the reel's average out of the roulette + // 6: Cram it all in UINT32 targetpower = roulette->dist; // fill roulette with items around this value! - UINT32 powers[NUMKARTRESULTS]; // how strong is each item? + UINT32 powers[NUMKARTRESULTS]; // how strong is each item? think of this as a "target distance" for this item to spawn at UINT32 deltas[NUMKARTRESULTS]; // how different is that strength from target? UINT32 candidates[NUMKARTRESULTS]; // how many of this item should we try to insert? UINT32 dupetolerance[NUMKARTRESULTS]; // how willing are we to select this item after already selecting it? higher values = lower dupe penalty boolean permit[NUMKARTRESULTS]; // is this item allowed? boolean rival = (player->bot && (player->botvars.rival || cv_levelskull.value)); - boolean mothfilter = true; // strip unusually weak items from reel? + boolean filterweakitems = true; // strip unusually weak items from reel? UINT8 reelsize = 15; // How many items to attempt to add in prepass? UINT32 humanscaler = 250 + (roulette->playing * 15); // Scaler that converts "useodds" style distances in odds tables to raw distances. - // Cache which items are permissible + // == ARE THESE ITEMS ALLOWED? + // We have a fuckton of rules about when items are allowed to show up, + // like limiting trap items at the end of the race, limiting strong + // items at the start of the race... Dynamic stuff, not always trivial. + // We're about to do a bunch of work with items, so let's cache them all. for (i = 1; i < NUMKARTRESULTS; i++) { - permit[i] = K_ShouldAllowItem(i, roulette); - - // CONS_Printf("%s permit prepass %d\n", cv_items[i-1].name, permit[i]); - - if (permit[i]) - permit[i] = K_ShouldPlayerAllowItem(i, player); - - // CONS_Printf("%s permit postpass %d\n", cv_items[i-1].name, permit[i]); + if (!K_TimingPermitsItem(i, roulette)) + permit[i] = false; + else if (!K_ShouldPlayerAllowItem(i, player)) + permit[i] = false; + else if (K_GetItemCooldown(i)) + permit[i] = false; + else if (!K_ItemEnabled(i)) + permit[i] = false; + else if (K_DenyShieldOdds(i)) + permit[i] = false; + else if (roulette && roulette->autoroulette == true && K_DenyAutoRouletteOdds(i)) + permit[i] = false; + else + permit[i] = true; } - // temp - i have no fucking clue how pointers work i am so sorry + // == ODDS TIME + // Set up the right item odds for the gametype we're in. for (i = 1; i < NUMKARTRESULTS; i++) { // NOTE: Battle odds are underspecified, we don't invoke roulettes in this mode! @@ -1424,14 +1433,14 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet { powers[i] = humanscaler * K_DynamicItemOddsBattle[i-1][0]; dupetolerance[i] = K_DynamicItemOddsBattle[i-1][1]; - mothfilter = false; + filterweakitems = false; } else if (specialstageinfo.valid == true) { powers[i] = humanscaler * K_DynamicItemOddsSpecial[i-1][0]; dupetolerance[i] = K_DynamicItemOddsSpecial[i-1][1]; - reelsize = 8; - mothfilter = false; + reelsize = 8; // Smaller roulette in Special because there are much fewer standard items. + filterweakitems = false; } else { @@ -1440,29 +1449,34 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } } - // null stuff that doesn't have odds + // == GTFO WEIRD ITEMS + // If something is set to distance 0 in its odds table, that means the item + // is completely ineligible for the gametype we're in, and should never be selected. for (i = 1; i < NUMKARTRESULTS; i++) { if (powers[i] == 0) { - // CONS_Printf("%s nulled\n", cv_items[i-1].name); permit[i] = false; } } - // Starting deltas + // == REEL CANDIDATE PREP + // Dynamic Roulette works by comparing an item's "ideal" distance to our current distance from 1st. + // It'll pick the most suitable item, do some math, then move on to the next most suitable item. + // Calculate starting deltas and clear out the "candidates" array that stores what we pick. for (i = 1; i < NUMKARTRESULTS; i++) { candidates[i] = 0; deltas[i] = min(targetpower - powers[i], powers[i] - targetpower); - // CONS_Printf("starting delta for %s is %d\n", cv_items[i-1].name, deltas[i]); } + // == LONELINESS DETECTION // A lot of items suck if no players are nearby to interact with them. // Should we bias towards items that get us back to the action? + // This will set the "lonely" flag to be used later. UINT32 lonelinessThreshold = 3*DISTVAR/2; - UINT32 toAttacker = lonelinessThreshold; - UINT32 toDefender = lonelinessThreshold; + UINT32 toAttacker = lonelinessThreshold; // Distance to the player trying to kill us. + UINT32 toDefender = lonelinessThreshold; // Distance to the player we are trying to kill. boolean lonely = false; if ((gametyperules & GTR_CIRCUIT) && specialstageinfo.valid == false) @@ -1483,32 +1497,39 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet if (toAttacker >= lonelinessThreshold && toDefender >= lonelinessThreshold && player->position > 1) lonely = true; - // let's start finding items to list - UINT8 added = 0; - UINT32 totalreelpower = 0; + // == INTRODUCE TRYHARD-EATING PREDATOR + // If the frontrunner's making a major breakaway, "break the rules" + // and insert the SPB into the roulette. This doesn't have to be + // incredibly forceful; there's a truly forced special case above. + fixed_t spb_odds = K_PercentSPBOdds(roulette, player->position); + + if ((gametyperules & GTR_CIRCUIT) + && specialstageinfo.valid == false + && (spb_odds > 0) & (spbplace == -1) + && (roulette->preexpdist >= powers[KITEM_SPB])) // SPECIAL CASE: Check raw distance instead of EXP-influenced target distance. + { + // When reenabling the SPB, we also adjust its delta to ensure that it has good odds of showing up. + // Players who are _seriously_ struggling are more likely to see Invinc or Rockets, since those items + // have a lower target distance, so we nudge the SPB towards them. + permit[KITEM_SPB] = true; + deltas[KITEM_SPB] = Easing_Linear(spb_odds, deltas[KITEM_SPB], 0); + } + + // == ITEM SELECTION + // All the prep work's done: let's pick out a sampler platter of items until we fill the reel. + UINT8 added = 0; // How many items added so far? + UINT32 totalreelpower = 0; // How much total item power in the reel? Used for an average later. for (i = 0; i < reelsize; i++) { UINT32 lowestdelta = INT32_MAX; size_t bestitem = 0; - // CONS_Printf("LOOP %d\n", i); - - // check each kartitem to see which is the best fit, - // based on what's closest to our target power - // (but ignore items that aren't allowed now) + // Each rep, get the legal item with the lowest delta... for (j = 1; j < NUMKARTRESULTS; j++) { - // CONS_Printf("precheck %s, perm %d CD %d\n", cv_items[j-1].name, permit[j], K_GetItemCooldown(j)); - if (!permit[j]) continue; - if (K_GetItemCooldown(j)) - continue; - if (!K_ItemEnabled(j)) - continue; - - // CONS_Printf("checking %s, delta %d\n", cv_items[j-1].name, deltas[j]); if (lowestdelta > deltas[j]) { @@ -1517,117 +1538,115 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet } } - // couldn't find an item? goodbye lol + // Couldn't find any eligible items at all? GTFO. + // (This should never trigger, but you never know with the item switch menu.) if (bestitem == 0) break; - // UINT32 deltapenalty = (DISTVAR*4)^(candidates[bestitem])/dupetolerance[bestitem]; + // Impose a penalty to this item's delta, to bias against selecting it again. + // This is naively slashed by an item's "duplicate tolerance": + // lower tolerance means that an item is less likely to be reselected (it's "rarer"). UINT32 deltapenalty = 4*DISTVAR*(1+candidates[bestitem])/dupetolerance[bestitem]; + // Power items get better odds in frantic, or if you're the rival. + // (For the rival, this is way more likely to matter at lower skills, where they're + // worse at selecting their item—but it always matters in frantic gameplay.) if (K_IsItemPower(bestitem) && rival) deltapenalty = 3 * deltapenalty / 4; if (K_IsItemPower(bestitem) && franticitems) deltapenalty = 3 * deltapenalty / 4; + + // Conversely, if we're lonely, try not to reselect an item that wouldn't be useful to us + // without any players to use it on. if (lonely && K_IsItemUselessAlone(bestitem)) deltapenalty *= 2; + // Draw complex odds debugger. This one breaks down all the calcs in order. if (cv_kartdebugdistribution.value > 1) { UINT16 BASE_X = 18; UINT16 BASE_Y = 5+12*i; INT32 FLAGS = V_SNAPTOTOP|V_SNAPTOLEFT; - // V_DrawThinString(BASE_X + 100, BASE_Y, FLAGS, va("%s", cv_items[lowestindex-1].name)); V_DrawThinString(BASE_X + 35, BASE_Y, FLAGS, va("P%d", powers[bestitem]/humanscaler)); V_DrawThinString(BASE_X + 65, BASE_Y, FLAGS, va("D%d", deltas[bestitem]/humanscaler)); V_DrawThinString(BASE_X + 20, BASE_Y, FLAGS, va("%d", dupetolerance[bestitem])); - //V_DrawThinString(BASE_X + 70, BASE_Y, FLAGS, va("+%d", deltapenalty)); V_DrawFixedPatch(BASE_X*FRACUNIT, (BASE_Y-7)*FRACUNIT, (FRACUNIT >> 1), FLAGS, K_GetSmallStaticCachedItemPatch(bestitem), NULL); UINT8 amount = K_ItemResultToAmount(bestitem); if (amount > 1) - { V_DrawThinString(BASE_X, BASE_Y, FLAGS, va("x%d", amount)); - } } - // otherwise, prep it to be added and give it a duplicaton penalty, - // so that a different item is more likely to be inserted next + // Add the selected item to our list of candidates and update its working delta. candidates[bestitem]++; deltas[bestitem] += deltapenalty; + // Then update our ongoing average of the reel's power. totalreelpower += powers[bestitem]; added++; - - // CONS_Printf("added %s with candidates %d\n", cv_items[lowestindex-1].name, candidates[lowestindex]); } - // Introduce SPB to race if there's a major frontrun breakway - - fixed_t spb_odds = K_PercentSPBOdds(roulette, player->position); - - if ((gametyperules & GTR_CIRCUIT) - && specialstageinfo.valid == false - && (spb_odds > 0) & (spbplace == -1)) + if (added == 0) { - permit[KITEM_SPB] = true; - deltas[KITEM_SPB] = Easing_Linear(spb_odds, 3000, 0); + // Guess we're making circles now. + // Just do something that doesn't crash. + K_AddItemToReel(player, roulette, singleItem); + return; } - UINT8 debugcount = 0; - UINT32 meanreelpower = totalreelpower/max(added, 1); + UINT8 debugcount = 0; // For the "simple" odds debugger. + UINT32 meanreelpower = totalreelpower/max(added, 1); // Average power for the "moth filter". - // set up the list indices used to random-shuffle the ro ulette + // == PREP FOR ADDING TO THE ROULETTE REEL + // Sal's prior work for this is rock-solid. + // This fills the spawnChance array with a rolling count of items, + // so that we can loop upward through it until we hit our random index. for (i = 1; i < NUMKARTRESULTS; i++) { - // filter items vastly too weak for this reel - boolean reject = (powers[i] + DISTVAR < meanreelpower); - - if (!mothfilter) - reject = false; + // If an item is far too week for this reel, reject it. + // This can happen in regions of the odds with a lot of items that + // don't really like to be duplicated. Favor the player; high-rolling + // feels exciting, low-rolling feels punishing! + boolean reject = (filterweakitems) && (powers[i] + DISTVAR < meanreelpower); + // Before we actually apply that rejection, draw the simple odds debugger. + // This one is just to watch the distribution for vibes as you drive around. if (cv_kartdebugdistribution.value && candidates[i]) { UINT16 BASE_X = 280; UINT16 BASE_Y = 5+12*debugcount; INT32 FLAGS = V_SNAPTOTOP|V_SNAPTORIGHT; - V_DrawThinString(BASE_X - 12, 5, FLAGS, va("%d", targetpower/humanscaler)); - V_DrawThinString(BASE_X - 12, 5+12, FLAGS, va("%d", toAttacker)); V_DrawThinString(BASE_X - 12, 5+24, FLAGS, va("%d", toDefender)); if (lonely) V_DrawThinString(BASE_X - 12, 5+36, FLAGS, va(":(")); - for(UINT8 k = 0; k < candidates[i]; k++) V_DrawFixedPatch((BASE_X + 3*k)*FRACUNIT, (BASE_Y-7)*FRACUNIT, (FRACUNIT >> 1), FLAGS, K_GetSmallStaticCachedItemPatch(i), NULL); - UINT8 amount = K_ItemResultToAmount(i); if (amount > 1) - { V_DrawThinString(BASE_X, BASE_Y, FLAGS, va("x%d", amount)); - } - if (reject) V_DrawThinString(BASE_X, BASE_Y, FLAGS|V_60TRANS, va("WEAK")); debugcount++; } - if (!reject) - { - spawnChance[i] = ( - totalSpawnChance += candidates[i] - ); - } + // Okay, apply the rejection now. + if (reject) + candidates[i] = 0; + + // Bump totalSpawnChance, write that rolling counter, and move on. + spawnChance[i] = ( + totalSpawnChance += candidates[i] + ); } - if (totalSpawnChance == 0) - { - // why did this fucking happen LOL - // don't crash - K_AddItemToReel(player, roulette, singleItem); - return; - } + if (dryrun) // We're being called from the debugger on a view conditional! + return; // This is net unsafe if we do things with side effects. GTFO! - // and insert all of our candidates into the roulette in a random order + // == FINALLY ADD THIS SHIT TO THE REEL + // Super simple: generate a random index, + // count up until we hit that index, + // insert that item and decrement everything after. while (totalSpawnChance > 0) { rngRoll = P_RandomKey(PR_ITEM_ROULETTE, totalSpawnChance); @@ -1636,13 +1655,10 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet continue; } - // CONS_Printf("adding %s, tsp %d\n", cv_items[i-1].name, totalSpawnChance); - K_AddItemToReel(player, roulette, i); for (; i < NUMKARTRESULTS; i++) { - // Be sure to fix the remaining items' odds too. if (spawnChance[i] > 0) { spawnChance[i]--; @@ -1663,7 +1679,7 @@ void K_StartItemRoulette(player_t *const player, boolean ringbox) itemroulette_t *const roulette = &player->itemRoulette; size_t i; - K_FillItemRouletteData(player, roulette, ringbox); + K_FillItemRouletteData(player, roulette, ringbox, false); if (roulette->autoroulette) roulette->index = P_RandomRange(PR_AUTOROULETTE, 0, roulette->itemListLen - 1); diff --git a/src/k_roulette.h b/src/k_roulette.h index f05354e93..03a4adb22 100644 --- a/src/k_roulette.h +++ b/src/k_roulette.h @@ -98,7 +98,7 @@ INT32 K_KartGetBattleOdds(const player_t *player, UINT8 pos, kartitems_t item); /*-------------------------------------------------- - void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox); + void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun); Fills out the item roulette struct when it is initially created. This function needs to be @@ -110,12 +110,13 @@ INT32 K_KartGetBattleOdds(const player_t *player, UINT8 pos, kartitems_t item); Can be NULL for generic use. roulette - The roulette data struct to fill out. ringbox - Is this roulette fill triggered by a just-respawned Ring Box? + dryrun - Are we calling this from the distribution debugger? Don't call RNG or write roulette data! Return:- N/A --------------------------------------------------*/ -void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox); +void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulette, boolean ringbox, boolean dryrun); /*-------------------------------------------------- From 5b841c11cb8b2911bcfff53c2cba75ced8e0b14e Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Wed, 21 Aug 2024 15:20:10 -0700 Subject: [PATCH 27/38] Use percentage for loneliness instead of hard breakpoint --- src/k_roulette.c | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 1f1ff3183..4d7159a98 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1473,29 +1473,34 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // == LONELINESS DETECTION // A lot of items suck if no players are nearby to interact with them. // Should we bias towards items that get us back to the action? - // This will set the "lonely" flag to be used later. - UINT32 lonelinessThreshold = 3*DISTVAR/2; + // This will set the "loneliness" percentage to be used later. + UINT32 lonelinessThreshold = 3*DISTVAR; // How far away can we be before items are considered useless? UINT32 toAttacker = lonelinessThreshold; // Distance to the player trying to kill us. UINT32 toDefender = lonelinessThreshold; // Distance to the player we are trying to kill. - boolean lonely = false; + fixed_t loneliness = 0; - if ((gametyperules & GTR_CIRCUIT) && specialstageinfo.valid == false) - { - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] == false || players[i].spectator == true || players[i].exiting) - continue; + if (player->position > 1) // Loneliness is expected when frontrunnning, don't influence their item table. + { + if ((gametyperules & GTR_CIRCUIT) && specialstageinfo.valid == false) + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true || players[i].exiting) + continue; - if (players[i].position == player->position + 1) - toAttacker = K_UndoMapScaling(players[i].distancetofinish - player->distancetofinish); + if (players[i].position == player->position + 1) + toAttacker = K_UndoMapScaling(players[i].distancetofinish - player->distancetofinish); - if (players[i].position == player->position - 1) - toDefender = K_UndoMapScaling(player->distancetofinish - players[i].distancetofinish); + if (players[i].position == player->position - 1) + toDefender = K_UndoMapScaling(player->distancetofinish - players[i].distancetofinish); + } } - } - if (toAttacker >= lonelinessThreshold && toDefender >= lonelinessThreshold && player->position > 1) - lonely = true; + // Your relationship to each closest player counts for half, but will be eased later. + // If you're far from an attacker but close to a defender, that Ballhog is still useful! + loneliness += min(FRACUNIT/2, FRACUNIT * toAttacker / lonelinessThreshold / 2); + loneliness += min(FRACUNIT/2, FRACUNIT * toDefender / lonelinessThreshold / 2); + } // == INTRODUCE TRYHARD-EATING PREDATOR // If the frontrunner's making a major breakaway, "break the rules" @@ -1558,8 +1563,8 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // Conversely, if we're lonely, try not to reselect an item that wouldn't be useful to us // without any players to use it on. - if (lonely && K_IsItemUselessAlone(bestitem)) - deltapenalty *= 2; + if (K_IsItemUselessAlone(bestitem)) + deltapenalty = Easing_InCubic(loneliness, deltapenalty, 3*deltapenalty); // Draw complex odds debugger. This one breaks down all the calcs in order. if (cv_kartdebugdistribution.value > 1) @@ -1618,8 +1623,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet V_DrawThinString(BASE_X - 12, 5, FLAGS, va("%d", targetpower/humanscaler)); V_DrawThinString(BASE_X - 12, 5+12, FLAGS, va("%d", toAttacker)); V_DrawThinString(BASE_X - 12, 5+24, FLAGS, va("%d", toDefender)); - if (lonely) - V_DrawThinString(BASE_X - 12, 5+36, FLAGS, va(":(")); + V_DrawThinString(BASE_X - 12, 5+36, FLAGS, va("%d", loneliness)); for(UINT8 k = 0; k < candidates[i]; k++) V_DrawFixedPatch((BASE_X + 3*k)*FRACUNIT, (BASE_Y-7)*FRACUNIT, (FRACUNIT >> 1), FLAGS, K_GetSmallStaticCachedItemPatch(i), NULL); UINT8 amount = K_ItemResultToAmount(i); From ef7c6dc086056fc3574366465f6cd82604703814 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Wed, 21 Aug 2024 20:22:02 -0700 Subject: [PATCH 28/38] Don't filter frontrunner roulette --- src/k_roulette.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/k_roulette.c b/src/k_roulette.c index 4d7159a98..5dad8542b 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1590,6 +1590,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet added++; } + // No items?! if (added == 0) { // Guess we're making circles now. @@ -1598,6 +1599,10 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet return; } + // Frontrunner roulette is precise, no need to filter it. + if (player->position <= 1) + filterweakitems = false; + UINT8 debugcount = 0; // For the "simple" odds debugger. UINT32 meanreelpower = totalreelpower/max(added, 1); // Average power for the "moth filter". From 69e37fe40baf28b81372578e9c1f4dd41cec6466 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Wed, 21 Aug 2024 21:00:11 -0700 Subject: [PATCH 29/38] Scale target power instead of distance values in large games (fixed weak item filter misfiring) --- src/k_roulette.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 5dad8542b..ce3067993 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1389,7 +1389,9 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // 5: Skim any items that are much weaker than the reel's average out of the roulette // 6: Cram it all in - UINT32 targetpower = roulette->dist; // fill roulette with items around this value! + fixed_t largegamescaler = FRACUNIT + (roulette->playing * (6*FRACUNIT/100)); // Spread out item odds in large games for a less insane experience. + UINT32 targetpower = FixedInt(FixedDiv(roulette->dist*FRACUNIT, largegamescaler)); // fill roulette with items around this value! + UINT32 powers[NUMKARTRESULTS]; // how strong is each item? think of this as a "target distance" for this item to spawn at UINT32 deltas[NUMKARTRESULTS]; // how different is that strength from target? UINT32 candidates[NUMKARTRESULTS]; // how many of this item should we try to insert? @@ -1399,7 +1401,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet boolean rival = (player->bot && (player->botvars.rival || cv_levelskull.value)); boolean filterweakitems = true; // strip unusually weak items from reel? UINT8 reelsize = 15; // How many items to attempt to add in prepass? - UINT32 humanscaler = 250 + (roulette->playing * 15); // Scaler that converts "useodds" style distances in odds tables to raw distances. + UINT32 humanscaler = 250; // Scaler that converts "useodds" style distances in odds tables to raw distances. Affects general item distance scale. // == ARE THESE ITEMS ALLOWED? // We have a fuckton of rules about when items are allowed to show up, From cd4bef9f24f53e38a1ea18cc617dc7c56cb31456 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Wed, 21 Aug 2024 21:24:00 -0700 Subject: [PATCH 30/38] Don't overflow when scaling large netgame distances --- src/k_roulette.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index ce3067993..cf16e9672 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1389,8 +1389,8 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // 5: Skim any items that are much weaker than the reel's average out of the roulette // 6: Cram it all in - fixed_t largegamescaler = FRACUNIT + (roulette->playing * (6*FRACUNIT/100)); // Spread out item odds in large games for a less insane experience. - UINT32 targetpower = FixedInt(FixedDiv(roulette->dist*FRACUNIT, largegamescaler)); // fill roulette with items around this value! + fixed_t largegamescaler = roulette->playing * 6 + 100; // Spread out item odds in large games for a less insane experience. + UINT32 targetpower = 100 * roulette->dist / largegamescaler; // fill roulette with items around this value! UINT32 powers[NUMKARTRESULTS]; // how strong is each item? think of this as a "target distance" for this item to spawn at UINT32 deltas[NUMKARTRESULTS]; // how different is that strength from target? From 42e23c7e9be8838ce476a4600186aec2a069ab8d Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 22 Aug 2024 00:56:45 -0700 Subject: [PATCH 31/38] Fix unusually rare Grow --- src/k_roulette.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index cf16e9672..9e0369151 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -91,7 +91,7 @@ static UINT32 K_DynamicItemOddsRace[NUMKARTRESULTS-1][2] = {10, 3}, // landmine {35, 4}, // ballhog {68, 6}, // selfpropelledbomb - {58, 7}, // grow + {65, 7}, // grow {71, 8}, // shrink {10, 1}, // lightningshield {30, 4}, // bubbleshield @@ -104,7 +104,7 @@ static UINT32 K_DynamicItemOddsRace[NUMKARTRESULTS-1][2] = {53, 5}, // gardentop {0, 0}, // gachabom {44, 9}, // dualsneaker - {61, 12}, // triplesneaker + {58, 12}, // triplesneaker {25, 2}, // triplebanana {30, 1}, // tripleorbinaut {40, 2}, // quadorbinaut @@ -1428,6 +1428,9 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // == ODDS TIME // Set up the right item odds for the gametype we're in. + + UINT32 maxpower = 0; // Clamp target power to the lowest item that exists, or some of the math gets hard to reason about. + for (i = 1; i < NUMKARTRESULTS; i++) { // NOTE: Battle odds are underspecified, we don't invoke roulettes in this mode! @@ -1449,8 +1452,12 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet powers[i] = humanscaler * K_DynamicItemOddsRace[i-1][0]; dupetolerance[i] = K_DynamicItemOddsRace[i-1][1]; } + + maxpower = max(maxpower, powers[i]); } + targetpower = min(maxpower, targetpower); // Make sure that we don't fall out of the bottom of the odds table. + // == GTFO WEIRD ITEMS // If something is set to distance 0 in its odds table, that means the item // is completely ineligible for the gametype we're in, and should never be selected. From 92220fcf6bb60f0d5c19e34d6bd28d68a5c9d2c6 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 22 Aug 2024 16:14:55 -0700 Subject: [PATCH 32/38] shitass sneaker buff gameplay proto --- src/k_kart.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/k_kart.c b/src/k_kart.c index d16b3604c..1fbd8473d 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -12974,6 +12974,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) else player->rocketsneakertimer -= 3*TICRATE; player->botvars.itemconfirm = 2*TICRATE; + player->overshield += TICRATE/2; // TEMP prototype } } else if (player->itemamount == 0) @@ -12989,6 +12990,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) { K_DoSneaker(player, 1); K_PlayBoostTaunt(player->mo); + player->overshield += TICRATE/2; // TEMP prototype + player->sneakertimer += TICRATE; // TEMP prototype player->itemamount--; player->botvars.itemconfirm = 0; } From 204a7ddabafb11afe0a87b1a18ee4417763342e1 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 22 Aug 2024 18:29:03 -0700 Subject: [PATCH 33/38] Earlier, more common sneakers --- src/k_roulette.c | 61 +++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 9e0369151..9dcccb292 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -80,35 +80,35 @@ static UINT32 K_DynamicItemOddsRace[NUMKARTRESULTS-1][2] = { // distance, duplication tolerance - {43, 9}, // sneaker - {73, 12}, // rocketsneaker - {70, 19}, // invincibility - {18, 6}, // banana - {17, 3}, // eggmark - {21, 14}, // orbinaut - {26, 7}, // jawz - {29, 8}, // mine - {10, 3}, // landmine - {35, 4}, // ballhog - {68, 6}, // selfpropelledbomb - {65, 7}, // grow - {71, 8}, // shrink - {10, 1}, // lightningshield - {30, 4}, // bubbleshield - {76, 9}, // flameshield - {10, 3}, // hyudoro + {22, 14}, // sneaker + {63, 12}, // rocketsneaker + {60, 19}, // invincibility + {8, 6}, // banana + {7, 3}, // eggmark + {11, 14}, // orbinaut + {16, 7}, // jawz + {19, 8}, // mine + {1, 3}, // landmine + {25, 4}, // ballhog + {58, 6}, // selfpropelledbomb + {55, 7}, // grow + {61, 8}, // shrink + {1, 1}, // lightningshield + {25, 4}, // bubbleshield + {66, 9}, // flameshield + {1, 3}, // hyudoro {0, 0}, // pogospring - {17, 4}, // superring + {7, 4}, // superring {0, 0}, // kitchensink - {10, 3}, // droptarget - {53, 5}, // gardentop + {1, 3}, // droptarget + {43, 5}, // gardentop {0, 0}, // gachabom - {44, 9}, // dualsneaker - {58, 12}, // triplesneaker - {25, 2}, // triplebanana - {30, 1}, // tripleorbinaut - {40, 2}, // quadorbinaut - {40, 4}, // dualjawz + {32, 14}, // dualsneaker + {42, 14}, // triplesneaker + {15, 2}, // triplebanana + {25, 1}, // tripleorbinaut + {35, 2}, // quadorbinaut + {30, 4}, // dualjawz {0, 0}, // triplegachabom }; @@ -1509,6 +1509,15 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // If you're far from an attacker but close to a defender, that Ballhog is still useful! loneliness += min(FRACUNIT/2, FRACUNIT * toAttacker / lonelinessThreshold / 2); loneliness += min(FRACUNIT/2, FRACUNIT * toDefender / lonelinessThreshold / 2); + + // Give interaction items a nudge against initial selection if you're lonely.. + for (i = 1; i < NUMKARTRESULTS; i++) + { + if (K_IsItemUselessAlone(i)) + { + deltas[i] = Easing_InCubic(loneliness, deltas[i], deltas[i] + DISTVAR); + } + } } // == INTRODUCE TRYHARD-EATING PREDATOR From 83f952e64b71f2faa89b92b70e25553013787560 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 22 Aug 2024 19:32:29 -0700 Subject: [PATCH 34/38] Lightning tweaks --- src/k_roulette.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 9dcccb292..2bf46c60a 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -83,13 +83,13 @@ static UINT32 K_DynamicItemOddsRace[NUMKARTRESULTS-1][2] = {22, 14}, // sneaker {63, 12}, // rocketsneaker {60, 19}, // invincibility - {8, 6}, // banana + {8, 4}, // banana {7, 3}, // eggmark - {11, 14}, // orbinaut - {16, 7}, // jawz - {19, 8}, // mine + {11, 4}, // orbinaut + {16, 4}, // jawz + {19, 4}, // mine {1, 3}, // landmine - {25, 4}, // ballhog + {25, 3}, // ballhog {58, 6}, // selfpropelledbomb {55, 7}, // grow {61, 8}, // shrink @@ -1099,6 +1099,24 @@ static boolean K_IsItemUselessAlone(kartitems_t item) } } +static boolean K_IsItemSpeed(kartitems_t item) +{ + switch (item) + { + case KITEM_ROCKETSNEAKER: + case KITEM_GROW: + case KITEM_INVINCIBILITY: + case KITEM_SNEAKER: + case KRITEM_DUALSNEAKER: + case KRITEM_TRIPLESNEAKER: + case KITEM_FLAMESHIELD: + case KITEM_SHRINK: + return true; + default: + return false; + } +} + // Which items are disallowed for this player's specific placement? static boolean K_ShouldPlayerAllowItem(kartitems_t item, const player_t *player) { @@ -1390,6 +1408,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // 6: Cram it all in fixed_t largegamescaler = roulette->playing * 6 + 100; // Spread out item odds in large games for a less insane experience. + largegamescaler = 100; // TEMP UINT32 targetpower = 100 * roulette->dist / largegamescaler; // fill roulette with items around this value! UINT32 powers[NUMKARTRESULTS]; // how strong is each item? think of this as a "target distance" for this item to spawn at @@ -1515,7 +1534,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet { if (K_IsItemUselessAlone(i)) { - deltas[i] = Easing_InCubic(loneliness, deltas[i], deltas[i] + DISTVAR); + deltas[i] = Easing_InCubic(loneliness, deltas[i], deltas[i] + (2*DISTVAR)); } } } From 5815635d51fabe825c1d77a22f9971a3e8971bec Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 22 Aug 2024 20:02:30 -0700 Subject: [PATCH 35/38] lightning round 2 --- src/k_roulette.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index 2bf46c60a..e99e87be8 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1408,7 +1408,6 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // 6: Cram it all in fixed_t largegamescaler = roulette->playing * 6 + 100; // Spread out item odds in large games for a less insane experience. - largegamescaler = 100; // TEMP UINT32 targetpower = 100 * roulette->dist / largegamescaler; // fill roulette with items around this value! UINT32 powers[NUMKARTRESULTS]; // how strong is each item? think of this as a "target distance" for this item to spawn at From e57f53016c866e50f6ddc37752acebeef3539f43 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 22 Aug 2024 20:06:30 -0700 Subject: [PATCH 36/38] Odds were never real --- src/k_roulette.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/k_roulette.c b/src/k_roulette.c index e99e87be8..d0ef0d2c6 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -84,7 +84,7 @@ static UINT32 K_DynamicItemOddsRace[NUMKARTRESULTS-1][2] = {63, 12}, // rocketsneaker {60, 19}, // invincibility {8, 4}, // banana - {7, 3}, // eggmark + {3, 1}, // eggmark {11, 4}, // orbinaut {16, 4}, // jawz {19, 4}, // mine @@ -92,7 +92,7 @@ static UINT32 K_DynamicItemOddsRace[NUMKARTRESULTS-1][2] = {25, 3}, // ballhog {58, 6}, // selfpropelledbomb {55, 7}, // grow - {61, 8}, // shrink + {70, 8}, // shrink {1, 1}, // lightningshield {25, 4}, // bubbleshield {66, 9}, // flameshield @@ -103,9 +103,9 @@ static UINT32 K_DynamicItemOddsRace[NUMKARTRESULTS-1][2] = {1, 3}, // droptarget {43, 5}, // gardentop {0, 0}, // gachabom - {32, 14}, // dualsneaker + {30, 14}, // dualsneaker {42, 14}, // triplesneaker - {15, 2}, // triplebanana + {25, 2}, // triplebanana {25, 1}, // tripleorbinaut {35, 2}, // quadorbinaut {30, 4}, // dualjawz From 36db25663374a858dde1385d5d7f52f1c54d9422 Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 22 Aug 2024 21:08:01 -0700 Subject: [PATCH 37/38] stronger exp in small games --- src/k_kart.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/k_kart.c b/src/k_kart.c index 1fbd8473d..7ace57292 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -14780,13 +14780,24 @@ fixed_t K_GetExpAdjustment(player_t *player) INT32 live_players = 0; - // Increase XP for each player you're beating... for (INT32 i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator || player == players+i) continue; live_players++; + } + + if (live_players < 8) + { + exp_power += (8 - live_players) * exp_power/4; + } + + // Increase XP for each player you're beating... + for (INT32 i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || player == players+i) + continue; if (player->position < players[i].position) result += exp_power; From a19b323d3669cd058cb44e7f1a23d4a27ecf1d6a Mon Sep 17 00:00:00 2001 From: Antonio Martinez Date: Thu, 22 Aug 2024 22:45:29 -0700 Subject: [PATCH 38/38] Amps readjustment --- src/k_kart.c | 13 +++++++++++-- src/p_inter.c | 5 ----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/k_kart.c b/src/k_kart.c index 7ace57292..1ddd8bad1 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -4007,8 +4007,10 @@ void K_SpawnAmps(player_t *player, UINT8 amps, mobj_t *impact) if (gametyperules & GTR_SPHERES) return; - // Give that Sonic guy some help. - UINT16 scaledamps = min(amps, amps * (10 + player->kartspeed - player->kartweight) / 10); + UINT16 scaledamps = min(amps, amps * (10 + (9-player->kartspeed) - (9-player->kartweight)) / 10); + + if (player->position <= 1) + scaledamps /= 2; for (int i = 0; i < (scaledamps/2); i++) { @@ -4052,6 +4054,13 @@ void K_AwardPlayerAmps(player_t *player, UINT8 amps) if (player->rings <= 0 && player->ampspending == 0) { + // Auto Overdrive! + // If this is a fresh OD, give 'em some extra juice to make up for lack of flexibility. + if (!player->overdrive && player->mo && !P_MobjWasRemoved(player->mo)) + { + S_StartSound(player->mo, sfx_gshac); + player->amps *= 2; + } K_Overdrive(player); } } diff --git a/src/p_inter.c b/src/p_inter.c index 2032c7930..52a0da4f5 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -3092,11 +3092,6 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da { K_DoPowerClash(target, inflictor); - if (inflictor->type != MT_PLAYER) - { - K_SpawnAmps(player, 5, inflictor); - } - if (inflictor->type == MT_SUPER_FLICKY) { Obj_BlockSuperFlicky(inflictor);