Ceci est une analyse de la manière dont le jeu génère aléatoirement les différentes caractéristiques d' Éden. Notez que cette analyse fut réalisée par BLouBLue sur la version v1.05 (pré-Afterbirth) et la version 1.06.T1 (Afterbirth+). L'analyse pour la version 1.7.9b (Repentance) a été réalisée par SarvaTathagata.
Lancement[ | ]
Quand le jeu est lancé, la principale chose à savoir est que celui-ci dérive un tas de sous-graines qui sont utilisées par les instances RNG dédiées aux objets, babioles, ressources, monstres, générations des étages et à la gestion des Personnages.
Cela permet de jouer une partie légèrement différemment, tout en ayant les mêmes objets aux mêmes endroits (pour la plupart, ce n'est pas tout à fait vrai).
Une sous-graine de "personnage" spécifique est utilisée pour la génération d' Éden. Cette instance RNG est utilisée durant deux phases:
- La génération des Cœurs, des ressources et attributs, avant l'initialisation du niveau;
- Le choix aléatoire des Cartes, des Pilules, des babioles et des objets, après l'initialisation du niveau.
Il revient à la sous-graine du Personnage avant chaque phase. Cela signifie que les mêmes nombres aléatoires peuvent sortir du RNG dans le même ordre pour les deux phases.
Cœurs[ | ]
Les Cœurs sont la première chose à être générée. Le jeu essaye de son mieux pour donner 3 cœurs entiers, coeurs rouges et coeurs d'âme confondus:
- Le jeu choisit aléatoirement
x = 0 à 3
coeurs rouges. - Il choisit aléatoirement entre
0 et (3 - x)
coeurs d'âme. - Si aucun cœur rouge n'est choisi pendant la première étape, le jeu garantit au joueur au moins 2 cœurs d'âme.
Il ne peut y avoir aucun coeur noir lors de cette phase. Les objets donnés par la suite peuvent dépasser ces limites.
Clés, bombes et pièces[ | ]
Éden a 1 chance sur 3 de commencer avec soit des Clés, des Bombes ou des Pièces. Si cette chance a été accordée, voici les résultats possibles:
Les objets données par la suite peuvent octroyer des ressources supplémentaires, amenant le total au-dessus de ces limites.
La chance de commencer avec une ressource est calculée comme suit:
// rand(min,max) renvoie un entier aléatoire entre min (inclus) et max (exclus)
if(rand(0, 3) == 0)
{ // 1 chance sur 3 que le joueur ait une ressource
tmp = rand(0, 3); // soit 0, 1 ou 2
if(tmp == 0)
donne 1 clé
else if(tmp == 1)
donne 1 + rand(0,2) bombes
else
donne 1 + rand(0,5) pièces
}
Attributs[ | ]
Les attributs sont basés sur les attributs de base d'Isaac. Un modificateur (valeur décimale) est aléatoirement choisi pour chacun des 6 attributs :
Attribut | Intervalle de modification d'Éden |
---|---|
dégâts | -1.00 à +1.00 nets |
vitesse | -0.15 à +0.15 |
débit | -0.75 à +0.75 -0.5 à +0.75 |
hauteur des larmes | -5.00 à +5.00 |
vitesse des larmes | -0.25 à +0.25 |
chance | -1.00 à +1.00 |
Voici à quoi ressemble l'initialisation des attributs:
// personnageRng.Random() renvoie une valeur décimale entre 0.0 et 1.0
Joueur->modificateurAttributDégâts = (personnageRng.Random() * 2.0f) + -1.0f;
Joueur->modificateurAttributVitesse = (personnageRng.Random() * 0.3f) + -0.15f;
Joueur->modificateurAttributCadenceTir = (personnageRng.Random() * 1.5f) + -0.75f;
Joueur->modificateurAttributPortée = (personnageRng.Random() * 10.0f) + -5.0f;
Joueur->modificateurAttributVitesseLarmes = (personnageRng.Random() * 0.5f) + -0.25f;
Joueur->modificateurAttributChance = (personnageRng.Random() * 2.0f) + -1.0f;
Objets de poche[ | ]
Les Pilules, Cartes, babioles et objets sont choisis après la génération de l'étage. Deux phases se produisent:
- Donne au joueur une chance d'avoir soit une pilule, soit une carte, soit une babiole;
- Tente de choisir 1 objet activable et 1 objet passif.
Donc, lors de la consommation d'un jeton d'Éden, il y a:
- 1 chance sur 3 d'obtenir une babiole;
- 1 chance sur 6 d'obtenir une Pilules;
- 1 chance sur 6 d'obtenir une carte;
- 1 chance sur 3 de ne rien obtenir.
Ces options sont mutuellement exclusives. On ne peut obtenir une babiole et une carte ou pilule. Voici à quoi ressemble la génération:
if(personnageRng.Random(3) > 0) { // 2 chances sur 3 d'obtenir une pilule, une carte ou rien
if(personnageRng.Random(2) > 0) { // 2 chances sur 3 * 1 chance sur 2 de ne rien obtenir
ne donne rien
}
else { // 2 chances sur 3 * 1 chance sur 2 chance d'obtenir une pilule ou une carte
if(personnageRng.Random(2) > 0) { // 1 chance sur 3 * 1 chance sur 2 de générer une pilule
donne une pilule aléatoire
}
else { // 1 chance sur 3 * 1 chance sur 2 de générer une carte
donne une carte aléatoire
}
}
}
else { // 1 chance sur 3 d'obtenir une babiole
donne une babiole aléatoire
}
En savoir plus sur la manière dont les objets de chaque type sont sélectionnés:
- Les couleurs et effets de Pilules sont mélangés en se basant sur une sous-graine dérivée de la graine principale. 9 ou 13 effets de pilules sont sélectionnés et affectés à aléatoirement chacune des couleurs. S'il en a eu la chance, Éden obtient aléatoirement l'une de ces pilules.
- Les babioles sont choisies aléatoirement parmi les babioles débloquées. Il y a 32/64 relancements aléatoires, et un autre en cas d'échec. Le jeu peut abandonner si le joueur n'a débloqué aucune babiole.
- Éden ne peut pas commencer avec l'une des cartes/runes suivantes:
- Toute rune ou pierre d'Âme,
- 2 of Trèfle, 2 of Carreau, 2 of Pique, 2 of Cœur, Joker,
- As de Trèfle, As de Carreau, As de Pique, As de Cœur,
- Dame de Cœur.
- 2 sets de cartes sont créés:
- Un set de cartes spéciales contenant:
- Carte Chaos, Carte de Crédit, Carte des Règles, Une Carte Limite Limite et Roi Suicidaire,
- Carte Chance, Carte ?, Fragment de Dé, Appel d'Urgence,
- Carte Sacrée, Croissance Colossale, Rappel Ancestral, Marche Temporelle,
- Clé Brisée, Dernière Carte
- Un set global contenant toutes les autres cartes exceptées celles étant interdites,
- Éden a 1 chance sur 25 d'obtenir aléatoirement une carte du set spécial et a 24 chances sur 25 d'en obtenir une du set global.
- Chaque carte d'arcane majeur a 1 chance sur 7 d'être remplacée par sa version renversée.
- Un set de cartes spéciales contenant:
Voici comment est réalisée la sélection des cartes, en pseudo-code:
// estRunesPermis et estCartesSpecialesPermis sont mis sur false lors de l'initialisation d'Éden.
// Les ID des cartes peuvent être trouvés dans les fichiers xml du jeu
// rngCarte.Random(min,max) renvoie un entier aléatoire entre min (inclus) et max (exclus)
// rngCarte.Random(max) renvoie un entier aléatoire entre 0 (inclus) et max (exclus, donc max-1 est la limite supérieure)
if(rngCarte.Random(25) > 0) // 24 chances sur 25
{
if(rngCarte.Random(10) || !estRunesPermis )
{
bool tmp = (rngCarte.Random(5) != 0) || !estCartesSpecialesPermis ;
if(tmp == false)
{
carte_premiere = CARD_2_OF_CLUBS;
carte_derniere = CARD_JOKER;
}
else
{
carte_premiere = CARD_0_THE_FOOL;
carte_derniere = CARD_XXI_THE_WORLD;
}
}
else
{
carte_premiere = RUNE_DESTRUCTION_HAGALAZ;
carte_derniere = RUNE_RESISTANCE_ALGIZ; // RUNE_BLACK dans AB+
}
}
else
{
carte_premiere = CARD_CHAOS_CARD;
carte_derniere = CARD_SUICIDE_KING; // CARD_ERA_WALK dans AB+
}
return rngCarte.Random(carte_premiere , carte_derniere + 1);
Voici comment est réalisée la sélection des cartes dans Repentance:
if (rngCarte.next() % 25 == 0) {
// Les cartes spéciales de Carte Chaos à Marche Temporelle, avec 2 cartes en plus dans Repentance.
int carte = (int)(rngCarte.next() % 15) + 42;
if(carte == 55){carte=78;} // Clé Brisée
if(carte == 56){carte=80;} // Dernière Carte
return carte;
}
// La vérification des runes et des cartes à jouer est retirée dans Repentance.
// Les cartes normales de 0 - Le Mat à XXI - Le Monde
int carte = (int)(cardRng.next() % 22) + 1;
// Les cartes d'arcanes majeures renversées, ajouté dans Repentance
if(cardRng.next() % 7 == 0){
carte += 55;
}
return carte;
Objets[ | ]
Le jeu tente 100 fois de remplir l'emplacement d'objet activable et un pour un objet passif. À chaque essai, il choisit aléatoirement un ID d'objet entre 1 et 346/552/732.
Il y a quelques ID d'objets désactivés qui demandent au jeu de re-choisir un nouvel ID et de passer à la prochaine tentative:
- #43: "Pills here!", un objet activable ne faisant rien et ressemblant à une pilule.
- Cela pourrait être un clin d'œil dans le code, référençant le "Pills Here!" quand le joueur trouve une pilule non-identifiée dans le jeu Flash.
- #59: "Tarot Card", un objet passif ressemblant à une carte et ne faisant rien.
- Cet ID appartient désormais au Livre de Bélial du Droit d'Aînesse de Judas.
- #61: Littéralement rien, et n'est pas défini dans items.xml.
- #235: Non disponible
- #238: Premier Fragment de Clé
- #239: Second Fragment de Clé
- #263: Non disponible
- #550: Pelle Brisée (morceau #1)
- #551: Pelle Brisée (morceau #2)
- #552: Pelle de Maman
Un objet sera désactivé uniquement s'il a la balise noeden.
Le premier candidat valide (objet débloqué) pour l'emplacement actif ou passif est sélectionné et ne peut pas être écrasé par les tentatives ultérieures (il y aura 100 tentatives peu importe le résultat).
Ces objets sont donnés au joueur après les 100 tentatives, que les deux emplacements aient été remplis ou non.
Par conséquent, Éden peut commencer pour le mieux avec 1 objet activable et 1 objet passif. Il est hautement improbable qu'une graine existante puisse donner à Éden seulement 1 ou aucun objet.
Comme rien n'ajoute de poids à certains objets spécifiquement, chaque combo d'objets a probablement environ 0.005% / 0.001% de chance d'être obtenu. Matériel de test: 1000 graines avec Éden.
C# Code[ | ]
Le code pour déterminer les objets d'Éden pour une graine donnée. Il manque le code pour les babioles, pilules et statistiques.
void Main()
{
var seeds = new List<uint>();
for(uint i = 0; i < uint.MaxValue; i++)
{
var startSeed = (uint)i;
var dropSeed = CalculatePlayerSeed(startSeed);
var items = CalculateEdenItems(dropSeed);
if (items.Active == 186 && (items.Passive == 276 || items.Passive == 108 || items.Passive == 301) && items.Card == 5)
{
seeds.Add(startSeed);
SeedToString(startSeed).Dump();
}
if (i % 100000000 == 0)
(":" + i).Dump();
}
seeds.Select(s => SeedToString(s)).ToArray().Dump();
}
//ItemCount impacte l'objet choisi par la RNG même si Éden ne peut pas commencer avec un objet qui a un ID supérieur à 552/732 dans Repentance
public static EdenItems CalculateEdenItems(uint dropSeed, int itemCount = 552)
{
var rng = new Rng(dropSeed, 0x1, 0x5, 0x13);
var trinket = 0;
var card = 0;
var pill = 0;
var hearts = 0;
var soulHearts = 0;
if ((rng.Next() % 3) == 0)
{
// Babiole
}
else if ((rng.Next() & 1) == 0)
{
if ((rng.Next() & 1) == 0)
{
card = GetCard(rng.Next());
}
else
{
// Pilule
var pillSeed = rng.Next();
}
}
var activeId = 0;
var passiveId = 0;
for (var i = 0; i < 100; i++)
{
int itemId = (int)(rng.Next() % itemCount) + 1;
// Liste des objets bannis
if (itemId > 552)
continue;
switch(itemId){
case 0x3B:
case 0xEB:
case 0x107:
case 0x2b:
case 0x3d:
case 0xee:
case 0xef:
case 0x226:
case 0x227:
case 0x228:
continue;
default:
break;
}
var itemType = ItemConfig[itemId];
if (itemType == ItemType.Active)
{
activeId = activeId != 0 ? activeId : itemId;
}
else if (itemType == ItemType.Passive || itemType == ItemType.Familiar)
{
passiveId = passiveId != 0 ? passiveId : itemId;
}
if (activeId != 0 && passiveId != 0)
break;
}
//Les coeurs rouges et d'âme sont traités dans Player::Init
var healthRng = new Rng(dropSeed, 0x1, 0x5, 0x13);
var halfHearts = (int)healthRng.Next() & 3;
hearts = halfHearts * 2;
soulHearts = ((int)healthRng.Next() % (4 - halfHearts)) * 2;
if (hearts == 0 && soulHearts < 4)
soulHearts = 4;
return new EdenItems(hearts, soulHearts, activeId, passiveId, trinket, card, pill);
}
public static int GetCard(uint seed, bool playing = false)
{
var cardRng = new Rng(seed, 0x5, 0x9, 0x7);
if (cardRng.Next() % 25 == 0) {
return (int)(cardRng.Next() % 13) + 42;
}
if (cardRng.Next() % 10 == 0)
{
// Rune
}
if (cardRng.Next() % 5 == 0 && playing)
{
// Carte à jouer
return (int)(cardRng.Next() % 9) + 23;
}
return (int)(cardRng.Next() % 22) + 1;
}
public static uint CalculatePlayerInitSeed(uint startSeed)
{
var startRng = new Rng(startSeed, 0x3, 0x17, 0x19);
// Graine d'étage
for (var i = 0; i < 0xD; i++)
startRng.Next();
return startRng.Next(); //Seeds::PlayerInitSeed
}
public static uint CalculatePlayerSeed(uint startSeed)
{
var playerInitSeed = CalculatePlayerInitSeed(startSeed);
// Ceci se produit dans Player::Init
var playerInitRng = new Rng(playerInitSeed, 0x1, 0xB, 0x10);
// Graine de CollectiblesRNG
playerInitRng.Next();
playerInitRng.Next();
playerInitRng.Next();
// Graine de CardsRNG
playerInitRng.Next();
return playerInitRng.Next(); // Entity::DropSeed
}
public class EdenItems
{
public int Hearts;
public int SoulHearts;
public int Active;
public int Passive;
public int Trinket;
public int Card;
public int Pill;
public EdenItems(int hearts, int soulHearts, int active, int passive, int trinket, int card, int pill)
{
Hearts = hearts;
SoulHearts = soulHearts;
Active = active;
Passive = passive;
Trinket = trinket;
Card = card;
Pill = pill;
}
}
static string SeedToString(uint num)
{
const string chars = "ABCDEFGHJKLMNPQRSTWXYZ01234V6789";
byte x = 0;
var tnum = num;
while (tnum != 0)
{
x += ((byte)tnum);
x += (byte)(x + (x >> 7));
tnum >>= 5;
}
num ^= 0x0FEF7FFD;
tnum = (num) << 8 | x;
var ret = new char[8];
for (int i = 0; i < 6; i++)
{
ret[i] = chars[(int)(num >> (27 - (i * 5)) & 0x1F)];
}
ret[6] = chars[(int)(tnum >> 5 & 0x1F)];
ret[7] = chars[(int)(tnum & 0x1F)];
return new string(ret);
}
public class Rng
{
public uint Seed;
public int Shift1;
public int Shift2;
public int Shift3;
public uint Next()
{
var num = Seed;
num ^= num >> Shift1;
num ^= num << Shift2;
num ^= num >> Shift3;
Seed = num;
return num;
}
public Rng(uint seed, int s1, int s2, int s3) {
this.Seed = seed;
Shift1 = s1;
Shift2 = s2;
Shift3 = s3;
}
};
public enum ItemType {
Null = 0,
Passive = 1,
Trinket = 2,
Active = 3,
Familiar = 4,
}
public static ItemType[] ItemConfig = GetItemConfig();
public static ItemType[] GetItemConfig() {
var itemConfig = new ItemType[553];
return itemConfig;
}
Le code complet peut être trouvé ici ou pour la version Repentance ici