Arena/EnemyMaker.cs
using System;
/// <summary>
/// Builds an opponent. Duels: a ragged fellow prisoner with next to no gear. Arena: a gladiator around
/// your level with gear their level allows.
/// </summary>
public static class EnemyMaker
{
// stat weights: Str, Atk, Def, Vit, Agi, Sta, Cha, Mag
static readonly (string Title, float[] Weights)[] Styles =
{
("Brute", new[] { 3f, 1.5f, 1f, 2.5f, 0.5f, 1.5f, 0.5f, 0.2f }),
("Duelist", new[] { 1.5f, 3f, 1f, 1.5f, 2.5f, 1.5f, 0.5f, 0.3f }),
("Showman", new[] { 1.5f, 1.5f, 0.5f, 1.5f, 1.5f, 2f, 3f, 0.3f }),
("Tank", new[] { 1.5f, 1f, 3f, 3f, 0.5f, 2f, 0.5f, 0.2f }),
("Berserker",new[] { 3f, 2f, 0.5f, 1.5f, 1.5f, 2.5f, 0.5f, 0.2f }),
("Mage", new[] { 1f, 1.5f, 1f, 1.5f, 1.5f, 1.5f, 1f, 3f }),
};
public static void Build( Gladiator g, Profile player, FightMode mode )
{
var rng = Random.Shared;
bool duel = mode == FightMode.Duel;
int level = duel ? Math.Max( 1, player.Level - rng.Int( 0, 1 ) ) : Math.Max( 1, player.Level + rng.Int( -1, 1 ) );
g.Level = level;
g.DisplayName = rng.FromArray( duel ? Catalog.PrisonerNames : Catalog.EnemyNames );
// a bit less than your budget in the dungeon, the same as you in the arena
int budget = Profile.StatCount * Profile.StartStat + Profile.StartPoints + (level - 1) * 5 - (duel ? 2 : 0);
var style = duel ? Styles[rng.Int( 0, 4 )] : rng.FromArray( Styles );
g.Title = duel ? rng.FromArray( Catalog.PrisonerTitles ) : $"Level {level} {style.Title.ToLower()}";
var w = style.Weights;
var stats = new int[Profile.StatCount];
for ( int i = 0; i < stats.Length; i++ ) stats[i] = Profile.StartStat;
int left = budget - stats.Length * Profile.StartStat;
float total = w.Sum();
while ( left > 0 )
{
float r = rng.Float( 0, total );
int pick = 0;
while ( pick < stats.Length - 1 && r > w[pick] ) { r -= w[pick]; pick++; }
stats[pick]++;
left--;
}
g.Strength = stats[0]; g.Attack = stats[1]; g.Defence = stats[2]; g.Vitality = stats[3];
g.Agility = stats[4]; g.Stamina = stats[5]; g.Charisma = stats[6]; g.Magicka = stats[7];
var look = Look.Default;
look.Race = rng.Float() < 0.55f ? 0 : rng.Int( 1, Catalog.Races.Length - 1 );
look.Skin = rng.Int( 0, Catalog.Races[look.Race].Skins.Length - 1 );
look.Hair = rng.Float() < 0.15f ? 0 : rng.Int( 1, Catalog.HairCount );
look.Beard = rng.Float() < 0.5f ? 0 : rng.Int( 1, Catalog.BeardCount );
look.HairColour = rng.Int( 0, Catalog.HairColours.Length - 1 );
look.Cloth = rng.Int( 0, Catalog.ClothColours.Length - 1 );
if ( duel )
{
// prisoners: a cheap weapon and a rag or two, nothing more
look.Weapon = rng.Int( 1, 5 );
look.Chest = rng.Float() < 0.8f ? 0 : -1;
if ( rng.Float() < 0.4f ) look.Helm = rng.Int( 0, 1 );
if ( rng.Float() < 0.25f ) look.Boots = 0;
if ( look.Chest < 0 && look.Helm < 0 ) look.Chest = 0;
}
else
{
// best weapon their strength allows, sometimes one worse
int weapon = Catalog.Weapons.Where( x => x.Strength <= g.Strength ).Max( x => x.Id );
if ( weapon > 1 && rng.Float() < 0.3f ) weapon--;
look.Weapon = Math.Max( 1, weapon );
// each slot: something their level allows, with a few gaps
foreach ( var slot in Catalog.Slots )
{
var allowed = Catalog.Gear( slot ).Where( x => x.Level <= level ).ToList();
if ( allowed.Count == 0 || rng.Float() < (slot == GearSlot.Shield ? 0.45f : 0.2f) ) continue;
int pick = allowed.Count - 1 - Math.Min( allowed.Count - 1, rng.Int( 0, 2 ) );
look.Set( slot, allowed[pick].Index );
}
// magic weapons and spells show up as they get stronger
if ( level >= 5 && rng.Float() < 0.25f + level * 0.02f )
{
g.Enchant = (Enchant)rng.Int( 1, 4 );
g.EnchantTier = level >= 16 ? rng.Int( 2, 3 ) : level >= 10 ? rng.Int( 1, 2 ) : 1;
}
foreach ( var s in Catalog.Spells )
if ( g.Magicka >= s.MinMagicka && rng.Float() < 0.5f ) g.SpellBits |= 1 << (int)s.Id;
if ( level >= 4 && rng.Float() < 0.4f ) g.HealthPotions = 1;
}
g.SetLook( look );
}
}