mirror of
https://github.com/mvvasilev/last_light.git
synced 2025-04-19 12:49:52 +03:00
337 lines
8.2 KiB
Go
337 lines
8.2 KiB
Go
package model
|
|
|
|
import (
|
|
"math/rand"
|
|
"mvvasilev/last_light/engine"
|
|
"slices"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"github.com/gdamore/tcell/v2"
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
const MaxNumberOfModifiers = 6
|
|
|
|
type RarityTable struct {
|
|
table []ItemRarity
|
|
}
|
|
|
|
func CreateRarityTable() *RarityTable {
|
|
return &RarityTable{
|
|
table: make([]ItemRarity, 0),
|
|
}
|
|
}
|
|
|
|
func (igt *RarityTable) Add(weight int, rarity ItemRarity) {
|
|
for range weight {
|
|
igt.table = append(igt.table, rarity)
|
|
}
|
|
}
|
|
|
|
func (igt *RarityTable) Generate() ItemRarity {
|
|
return igt.table[rand.Intn(len(igt.table))]
|
|
}
|
|
|
|
type ItemSupplier func() Item
|
|
|
|
func EmptyItemSupplier() ItemSupplier {
|
|
return func() Item {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func ItemSupplierOf(item Item) ItemSupplier {
|
|
return func() Item {
|
|
return item
|
|
}
|
|
}
|
|
|
|
func ItemSupplierOfGeneratedPrototype(prototype Item, rarities map[int]ItemRarity) ItemSupplier {
|
|
rarityTable := CreateRarityTable()
|
|
|
|
for k, v := range rarities {
|
|
rarityTable.Add(k, v)
|
|
}
|
|
|
|
return func() Item {
|
|
return GenerateItemOfTypeAndRarity(prototype, rarityTable.Generate())
|
|
}
|
|
}
|
|
|
|
type LootTable struct {
|
|
table []ItemSupplier
|
|
}
|
|
|
|
func CreateLootTable() *LootTable {
|
|
return &LootTable{
|
|
table: make([]ItemSupplier, 0),
|
|
}
|
|
}
|
|
|
|
func (igt *LootTable) Add(weight int, createItemFunction ItemSupplier) {
|
|
for range weight {
|
|
igt.table = append(igt.table, createItemFunction)
|
|
}
|
|
}
|
|
|
|
func (igt *LootTable) Generate() Item {
|
|
return igt.table[rand.Intn(len(igt.table))]()
|
|
}
|
|
|
|
type ItemRarity int
|
|
|
|
const (
|
|
ItemRarity_Common ItemRarity = 0
|
|
ItemRarity_Uncommon ItemRarity = 1
|
|
ItemRarity_Rare ItemRarity = 2
|
|
ItemRarity_Epic ItemRarity = 3
|
|
ItemRarity_Legendary ItemRarity = 4
|
|
)
|
|
|
|
func pointPerRarity(rarity ItemRarity) int {
|
|
switch rarity {
|
|
case ItemRarity_Common:
|
|
return 0
|
|
case ItemRarity_Uncommon:
|
|
return 3
|
|
case ItemRarity_Rare:
|
|
return 5
|
|
case ItemRarity_Epic:
|
|
return 8
|
|
case ItemRarity_Legendary:
|
|
return 13
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func generateUniqueItemName() string {
|
|
starts := []string{
|
|
"du", "nol", "ma", "re",
|
|
"ka", "gro", "hru", "lo",
|
|
"ara", "ke", "ko", "uro",
|
|
"ne", "pe", "pa", "pho",
|
|
}
|
|
|
|
middles := []string{
|
|
"kora", "duru", "kolku", "dila",
|
|
"luio", "ghro", "kelma", "riga",
|
|
"fela", "fiya", "numa", "ruta",
|
|
}
|
|
|
|
end := []string{
|
|
"dum", "dor", "dar", "thar",
|
|
"thor", "thum", "hor", "hum",
|
|
"her", "kom", "kur", "kyr",
|
|
"mor", "mar", "man", "kum",
|
|
"tum",
|
|
}
|
|
|
|
name := starts[rand.Intn(len(starts))] + middles[rand.Intn(len(middles))] + end[rand.Intn(len(end))]
|
|
|
|
r, size := utf8.DecodeRuneInString(name)
|
|
|
|
return string(unicode.ToUpper(r)) + name[size:]
|
|
}
|
|
|
|
func randomAdjective() string {
|
|
adjectives := []string{
|
|
"shiny", "gruesome", "sharp", "tattered",
|
|
"mediocre", "unusual", "bright", "rusty",
|
|
"dreadful", "exceptional", "old", "bent",
|
|
"ancient", "crude", "dented", "cool",
|
|
}
|
|
|
|
adj := adjectives[rand.Intn(len(adjectives))]
|
|
|
|
r, size := utf8.DecodeRuneInString(adj)
|
|
|
|
return string(unicode.ToUpper(r)) + adj[size:]
|
|
}
|
|
|
|
func randomSuffix() string {
|
|
suffixes := []string{
|
|
"of the Monkey", "of the Tiger", "of the Elephant", "of the Slug",
|
|
"of Elven Make",
|
|
}
|
|
|
|
return suffixes[rand.Intn(len(suffixes))]
|
|
}
|
|
|
|
func generateItemName(existingItemName string, rarity ItemRarity) (string, tcell.Style) {
|
|
switch rarity {
|
|
case ItemRarity_Common:
|
|
return existingItemName, tcell.StyleDefault
|
|
case ItemRarity_Uncommon:
|
|
return randomAdjective() + " " + existingItemName, tcell.StyleDefault.Foreground(tcell.ColorLime)
|
|
case ItemRarity_Rare:
|
|
return existingItemName + " " + randomSuffix(), tcell.StyleDefault.Foreground(tcell.ColorBlue)
|
|
case ItemRarity_Epic:
|
|
return randomAdjective() + " " + existingItemName + " " + randomSuffix(), tcell.StyleDefault.Foreground(tcell.ColorPurple)
|
|
case ItemRarity_Legendary:
|
|
return generateUniqueItemName() + ", Legendary " + existingItemName, tcell.StyleDefault.Foreground(tcell.ColorOrange).Attributes(tcell.AttrBold)
|
|
default:
|
|
return existingItemName, tcell.StyleDefault
|
|
}
|
|
}
|
|
|
|
func randomStat(metaItemTypes []ItemMetaType) Stat {
|
|
stats := make(map[ItemMetaType][]Stat, 0)
|
|
|
|
stats[MetaItemType_Weapon] = []Stat{
|
|
Stat_Attributes_Strength,
|
|
Stat_Attributes_Dexterity,
|
|
Stat_Attributes_Intelligence,
|
|
Stat_Attributes_Constitution,
|
|
Stat_TotalPrecisionBonus,
|
|
}
|
|
|
|
stats[MetaItemType_Physical_Weapon] = []Stat{
|
|
Stat_PhysicalPrecisionBonus,
|
|
Stat_DamageBonus_Physical_Slashing,
|
|
Stat_DamageBonus_Physical_Piercing,
|
|
Stat_DamageBonus_Physical_Bludgeoning,
|
|
Stat_DamageBonus_Magic_Fire,
|
|
Stat_DamageBonus_Magic_Cold,
|
|
Stat_DamageBonus_Magic_Necrotic,
|
|
Stat_DamageBonus_Magic_Thunder,
|
|
Stat_DamageBonus_Magic_Acid,
|
|
Stat_DamageBonus_Magic_Poison,
|
|
}
|
|
|
|
stats[MetaItemType_Magic_Weapon] = []Stat{
|
|
Stat_MagicPrecisionBonus,
|
|
Stat_DamageBonus_Magic_Fire,
|
|
Stat_DamageBonus_Magic_Cold,
|
|
Stat_DamageBonus_Magic_Necrotic,
|
|
Stat_DamageBonus_Magic_Thunder,
|
|
Stat_DamageBonus_Magic_Acid,
|
|
Stat_DamageBonus_Magic_Poison,
|
|
}
|
|
|
|
stats[MetaItemType_Armour] = []Stat{
|
|
Stat_EvasionBonus,
|
|
Stat_DamageBonus_Physical_Unarmed,
|
|
Stat_MaxHealthBonus,
|
|
}
|
|
|
|
stats[MetaItemType_Magic_Armour] = []Stat{
|
|
Stat_MagicPrecisionBonus,
|
|
Stat_DamageBonus_Magic_Fire,
|
|
Stat_DamageBonus_Magic_Cold,
|
|
Stat_DamageBonus_Magic_Necrotic,
|
|
Stat_DamageBonus_Magic_Thunder,
|
|
Stat_DamageBonus_Magic_Acid,
|
|
Stat_DamageBonus_Magic_Poison,
|
|
Stat_ResistanceBonus_Magic_Acid,
|
|
Stat_ResistanceBonus_Magic_Cold,
|
|
Stat_ResistanceBonus_Magic_Fire,
|
|
Stat_ResistanceBonus_Magic_Necrotic,
|
|
Stat_ResistanceBonus_Magic_Poison,
|
|
Stat_ResistanceBonus_Magic_Thunder,
|
|
}
|
|
|
|
stats[MetaItemType_Physical_Armour] = []Stat{
|
|
Stat_PhysicalPrecisionBonus,
|
|
Stat_DamageBonus_Physical_Slashing,
|
|
Stat_DamageBonus_Physical_Piercing,
|
|
Stat_DamageBonus_Physical_Bludgeoning,
|
|
Stat_ResistanceBonus_Physical_Bludgeoning,
|
|
Stat_ResistanceBonus_Physical_Piercing,
|
|
Stat_ResistanceBonus_Physical_Slashing,
|
|
Stat_ResistanceBonus_Physical_Unarmed,
|
|
}
|
|
|
|
possibleStats := make([]Stat, 0, 10)
|
|
|
|
for _, mt := range metaItemTypes {
|
|
possibleStats = append(possibleStats, stats[mt]...)
|
|
}
|
|
|
|
return slices.Compact(possibleStats)[rand.Intn(len(stats))]
|
|
}
|
|
|
|
func generateItemStatModifiers(itemMetaTypes []ItemMetaType, rarity ItemRarity) []StatModifier {
|
|
points := pointPerRarity(rarity)
|
|
modifiers := make(map[Stat]*StatModifier, 0)
|
|
|
|
for {
|
|
// If no points remain, or if the number of modifiers on the item reaches the maximum
|
|
if points <= 0 || len(modifiers) == MaxNumberOfModifiers {
|
|
break
|
|
}
|
|
|
|
// Random chance to increase or decrease a stat
|
|
modAmount := engine.RandInt(-points, points)
|
|
|
|
if modAmount == 0 {
|
|
continue
|
|
}
|
|
|
|
stat := randomStat(itemMetaTypes)
|
|
|
|
existingForStat := modifiers[stat]
|
|
|
|
// If this stat modifier already exists on the item, add the new modification amount to the old
|
|
if existingForStat != nil {
|
|
existingForStat.Bonus += modAmount
|
|
|
|
// If the added amount is 0, remove the modifier
|
|
if existingForStat.Bonus == 0 {
|
|
delete(modifiers, stat)
|
|
} else {
|
|
modifiers[stat] = existingForStat
|
|
}
|
|
|
|
} else {
|
|
// Otherwise, append a new stat modifier
|
|
modifiers[stat] = &StatModifier{
|
|
Id: StatModifierId(uuid.New().String()),
|
|
Stat: stat,
|
|
Bonus: modAmount,
|
|
}
|
|
}
|
|
|
|
// Decrease amount of points left by absolute value
|
|
points -= engine.AbsInt(modAmount)
|
|
}
|
|
|
|
vals := make([]StatModifier, 0, len(modifiers))
|
|
|
|
for _, v := range modifiers {
|
|
vals = append(vals, *v)
|
|
}
|
|
|
|
return vals
|
|
}
|
|
|
|
// Each rarity gets an amount of generation points, the higher the rarity, the more points
|
|
// Each stat modifier consumes points. The higher the stat bonus, the more points it consumes.
|
|
func GenerateItemOfTypeAndRarity(prototype Item, rarity ItemRarity) Item {
|
|
if prototype.Named() == nil {
|
|
return prototype
|
|
}
|
|
|
|
if prototype.MetaTypes() == nil {
|
|
return prototype
|
|
}
|
|
|
|
existingName := prototype.Named().Name
|
|
metaTypes := prototype.MetaTypes().Types
|
|
name, style := generateItemName(existingName, rarity)
|
|
statModifiers := generateItemStatModifiers(metaTypes, rarity)
|
|
|
|
return createBaseItem(
|
|
prototype.Type(),
|
|
prototype.TileIcon(),
|
|
prototype.Icon(),
|
|
prototype.Style(),
|
|
item_WithName(name, style),
|
|
item_WithDescription(prototype.Described().Description, prototype.Described().Style),
|
|
item_WithDamaging(prototype.Damaging().IsRanged, prototype.Damaging().DamageRoll),
|
|
item_WithEquippable(prototype.Equippable().Slot),
|
|
item_WithStatModifiers(statModifiers),
|
|
item_WithMetaTypes(metaTypes),
|
|
)
|
|
}
|