mirror of
https://github.com/IvarK/AntimatterDimensionsSourceCode.git
synced 2024-11-12 23:23:20 +00:00
Implement uniform glyph effect logic
This commit is contained in:
parent
7b3df7c8af
commit
18c29f8371
@ -53,6 +53,10 @@ class GlyphRNG {
|
||||
}
|
||||
|
||||
export const GlyphGenerator = {
|
||||
// Glyph choices will have more uniformly-distributed properties up until this reality count.
|
||||
// Should be a multiple of 5, as the uniformity conditions only work within groups of 5 realities
|
||||
uniformityThreshold: 20,
|
||||
|
||||
fakeSeed: Date.now() % Math.pow(2, 32),
|
||||
fakeSecondGaussian: null,
|
||||
/* eslint-disable lines-between-class-members */
|
||||
@ -282,31 +286,70 @@ export const GlyphGenerator = {
|
||||
},
|
||||
|
||||
/**
|
||||
* If we call the set of basic glyph types excluding power as 1, basic glyph types excluding infinity as 2, etc.
|
||||
* then the problem of multiple-reality type uniformity can be reframed as finding a certain sequence of the
|
||||
* numbers 1-5 which uphold some definition of "uniformity" which is friendly to the player.
|
||||
* Here we use the player's initial RNG seed to determine a sequence of numbers 1-5 which are grouped
|
||||
* together such that every block of 5 contains a pseudorandom permutation of the numbers 1-5, and then uses the
|
||||
* current reality count to select an index from this sequence. (eg. 43521 31524 51342 24135 45132...)
|
||||
* This makes types more "uniform" by ensuring that any individual glyph type is never *repeatedly* absent for more
|
||||
* than 2 realities in a row, as well as ensuring that trends of long-term type absences never happen
|
||||
* To generate glyphs with a "uniformly random" effect spread, we effectively need to generate all the glyphs in
|
||||
* uniform groups of some size at once, and then select from that generated group. In this case, we've decided
|
||||
* that a group which satisfies uniformity is that of 5 realities, such that all 20 choices amongst the group
|
||||
* must contain each individual glyph effect at least once. This makes types more "uniform" by ensuring that
|
||||
* any individual glyph type is never *repeatedly* absent for more than 2 realities in a row (which can only
|
||||
* happen between groups), as well as ensuring that trends of long-term type/effect absences never happen
|
||||
* Note: At this point, realityCount should be the number of realities BEFORE processing completes (ie. the first
|
||||
* random generated set begins at a parameter of 1)
|
||||
*/
|
||||
uniformRandomTypes(realityCount) {
|
||||
// Reality count divided by 5 is used as an input to generate a random permutation of 1-5, while count mod 5
|
||||
// determines the index within that block
|
||||
const setIndex = Math.floor((realityCount - 1) / 5);
|
||||
const permIndex = realityCount % 5;
|
||||
uniformGlyphs(level, rng, realityCount) {
|
||||
// Reality count divided by 5 to determine which group of 5 we're in, while count mod 5 determines the index
|
||||
// within that block. Note that we have a minus 1 because we want to exclude the first fixed glyph
|
||||
const groupNum = Math.floor((realityCount - 1) / 5);
|
||||
const groupIndex = (realityCount - 1) % 5;
|
||||
|
||||
// The usage of the initial seed is complicated in order to prevent future prediction without using information
|
||||
// not normally available in-game (ie. the console). This is primarily to make it appear less predictable overall
|
||||
// not normally available in-game (ie. the console). This makes it appear less predictable overall
|
||||
const initSeed = player.reality.initialSeed;
|
||||
const perm = permutationIndex(5, (31 + initSeed % 7) * setIndex + initSeed % 1123);
|
||||
const typePerm = permutationIndex(5, (31 + initSeed % 7) * groupNum + initSeed % 1123);
|
||||
|
||||
const types = [...BASIC_GLYPH_TYPES];
|
||||
types.splice(perm[permIndex], 1);
|
||||
return types;
|
||||
// Figure out a permutation index for each generated glyph type this reality by counting through the sets
|
||||
// for choices which have already been generated for group in previous realities
|
||||
const typePermIndex = Array.repeat(0, 5);
|
||||
for (let i = 0; i < groupIndex; i++) {
|
||||
for (let type = 0; type < 5; type++) {
|
||||
if (type !== typePerm[i]) typePermIndex[type]++;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine which effect needs to be added for uniformity (startID is a hardcoded array of the lowest ID glyph
|
||||
// effect of each type, in the same type order as BASIC_GLYPH_TYPES). We use type, initial seed, and group index
|
||||
// to pick a random permutation, again to make it less predictable and to make sure they're generally different
|
||||
const uniformEffects = [];
|
||||
const startID = [16, 12, 8, 0, 4];
|
||||
const typesThisReality = Array.range(0, 5);
|
||||
typesThisReality.splice(typePerm[groupIndex], 1);
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const type = typesThisReality[i];
|
||||
const effectPerm = permutationIndex(4, 6 * type + (7 + initSeed % 5) * groupNum + initSeed % 11);
|
||||
uniformEffects.push(startID[type] + effectPerm[typePermIndex[type]]);
|
||||
}
|
||||
|
||||
// Generate the glyphs without uniformity applied first, assuming 4 glyph choices early on, then add the new effect
|
||||
// Note that if this would give us more than 2 effects, we remove one of the existing ones (having extra effects
|
||||
// this early on *increases* RNG variance to an undesirable amount)
|
||||
const glyphs = [];
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
const newGlyph = GlyphGenerator.randomGlyph(level, rng, BASIC_GLYPH_TYPES[typesThisReality[i]]);
|
||||
if (countValuesFromBitmask(newGlyph.effects | (1 << uniformEffects[i])) > 2) {
|
||||
// Turn the existing effect bitmask into an array of IDs, deterministically remove one based on seed and
|
||||
// reality count, and then reconstruct the effect mask with the new one added
|
||||
const effectIDs = getGlyphEffectsFromBitmask(newGlyph.effects)
|
||||
.filter(eff => eff.isGenerated)
|
||||
.map(eff => eff.bitmaskIndex);
|
||||
effectIDs.splice((initSeed + realityCount) % effectIDs.length, 1);
|
||||
effectIDs.push(uniformEffects[i]);
|
||||
newGlyph.effects = effectIDs.reduce((mask, bit) => mask + (1 << bit), 0);
|
||||
} else {
|
||||
newGlyph.effects |= 1 << uniformEffects[i];
|
||||
}
|
||||
glyphs.push(newGlyph);
|
||||
}
|
||||
|
||||
return glyphs;
|
||||
},
|
||||
|
||||
getRNG(fake) {
|
||||
|
@ -38,16 +38,20 @@ export const GlyphSelection = {
|
||||
let glyphList = [];
|
||||
const rng = config.rng || new GlyphGenerator.RealGlyphRNG();
|
||||
const types = [];
|
||||
if (player.realities <= 20) {
|
||||
types.push(...GlyphGenerator.uniformRandomTypes(player.realities));
|
||||
|
||||
// To attempt to reduce RNG swing, we follow slightly different logic early on in order
|
||||
// to spread out types and effects more equally for the first few realities
|
||||
if (player.realities <= GlyphGenerator.uniformityThreshold) {
|
||||
glyphList = GlyphGenerator.uniformGlyphs(level, rng, player.realities);
|
||||
} else {
|
||||
for (let out = 0; out < count; ++out) {
|
||||
types.push(GlyphGenerator.randomType(rng, types));
|
||||
}
|
||||
for (let out = 0; out < count; ++out) {
|
||||
glyphList.push(GlyphGenerator.randomGlyph(level, rng, types[out]));
|
||||
}
|
||||
}
|
||||
for (let out = 0; out < count; ++out) {
|
||||
glyphList.push(GlyphGenerator.randomGlyph(level, rng, types[out]));
|
||||
}
|
||||
|
||||
this.glyphUncommonGuarantee(glyphList, rng);
|
||||
// If we generated extra choices due to always generating at least 4 choices,
|
||||
// we remove the extra choices here.
|
||||
@ -143,10 +147,6 @@ export function processManualReality(sacrifice, glyphID) {
|
||||
// If this is our first Reality, give them the companion and the starting power glyph.
|
||||
Glyphs.addToInventory(GlyphGenerator.startingGlyph(gainedGlyphLevel()));
|
||||
Glyphs.addToInventory(GlyphGenerator.companionGlyph(Currency.eternityPoints.value));
|
||||
// Uniform early glyph code is massively simplified if we also store the initial seed, and use that for
|
||||
// generation but the seed itself advances after every reality. Since they need to be the same value, on the
|
||||
// first random glyph we randomize the initial value and set the current value to it after the first reality
|
||||
player.reality.seed = player.reality.initialSeed;
|
||||
} else if (Perk.firstPerk.isEffectActive) {
|
||||
// If we have firstPerk, we pick from 4+ glyphs, and glyph generation functions as normal.
|
||||
GlyphSelection.generate(GlyphSelection.choiceCount);
|
||||
|
@ -223,7 +223,10 @@ GameStorage.migrations = {
|
||||
}
|
||||
player.news.totalSeen = Math.max(player.news.totalSeen, unique);
|
||||
}
|
||||
}
|
||||
},
|
||||
16: player => {
|
||||
player.reality.initialSeed = player.reality.seed;
|
||||
},
|
||||
},
|
||||
|
||||
normalizeTimespans(player) {
|
||||
|
@ -52,6 +52,11 @@ export class DilationTimeStudyState extends TimeStudyState {
|
||||
Points and capped at ${format("1e8000")} Eternity Points. This is due to balance changes made in the Reality
|
||||
update which affect the difficulty of reaching those amounts, such as the increased Time Dimension cost
|
||||
scaling above ${format("1e6000")}.`, {}, 3);
|
||||
|
||||
// Uniform early glyph code is massively simplified if we also store the initial seed, and use that for
|
||||
// generation but the seed itself advances after every reality. Since they need to be the same value and the
|
||||
// initial seed is set on save creation, we set the actual seed upon reality unlocking for the first time
|
||||
player.reality.seed = player.reality.initialSeed;
|
||||
EventHub.dispatch(GAME_EVENT.REALITY_FIRST_UNLOCKED);
|
||||
}
|
||||
if (!Perk.autounlockReality.isBought) Tab.reality.glyphs.show();
|
||||
|
Loading…
Reference in New Issue
Block a user