From bbd515bc57040a58b21c97dea1e5dd84b0f22cb6 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Mon, 25 Aug 2025 16:35:55 -0500 Subject: [PATCH] big item rework --- data/dk.db | Bin 73728 -> 81920 bytes go.mod | 2 +- go.sum | 4 +- internal/actions/move.go | 2 +- internal/actions/user_item.go | 360 ++++++++++++++++++++++++++++++--- internal/models/drops/drops.go | 101 --------- internal/models/items/items.go | 199 +++++++++++++++--- internal/routes/town.go | 7 +- sql/1_create_database.sql | 44 ---- sql/2_rework_items_system.sql | 88 ++++++++ templates/rightside.html | 6 + templates/town/shop.html | 9 +- 12 files changed, 618 insertions(+), 204 deletions(-) delete mode 100644 internal/models/drops/drops.go create mode 100644 sql/2_rework_items_system.sql diff --git a/data/dk.db b/data/dk.db index fda1a74e9e0d885a79853922352e8f6a4701710f..5794c0601ab878b9e130d38534eb330ae48310d9 100644 GIT binary patch literal 81920 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 literal 73728 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 3 { + return nil, fmt.Errorf("invalid slot number: %d", slot) + } + + updates := make(map[string]any) + newStats := calculateNewStatsForSlot(user, item, slot) + + // Update the specific slot + switch slot { + case 1: + updates["slot_1_id"] = item.ID + updates["slot_1_name"] = item.Name + case 2: + updates["slot_2_id"] = item.ID + updates["slot_2_name"] = item.Name + case 3: + updates["slot_3_id"] = item.ID + updates["slot_3_name"] = item.Name + } + + // Apply stat changes + updates["attack"] = newStats.Attack + updates["defense"] = newStats.Defense + updates["strength"] = newStats.Strength + updates["dexterity"] = newStats.Dexterity + updates["max_hp"] = newStats.MaxHP + updates["max_mp"] = newStats.MaxMP + updates["exp_bonus"] = newStats.ExpBonus + updates["gold_bonus"] = newStats.GoldBonus + + return updates, nil +} + +// UserUnequipItem removes an equipped item and recalculates stats +func UserUnequipItem(user *users.User, itemType int) (map[string]any, error) { + updates := make(map[string]any) + + switch itemType { + case items.TypeWeapon: + if user.WeaponID == 0 { + return nil, fmt.Errorf("no weapon equipped") + } + updates["weapon_id"] = 0 + updates["weapon_name"] = "" + + case items.TypeArmor: + if user.ArmorID == 0 { + return nil, fmt.Errorf("no armor equipped") + } + updates["armor_id"] = 0 + updates["armor_name"] = "" + + case items.TypeShield: + if user.ShieldID == 0 { + return nil, fmt.Errorf("no shield equipped") + } + updates["shield_id"] = 0 + updates["shield_name"] = "" + + default: + return nil, fmt.Errorf("invalid item type for unequip: %d", itemType) + } + + // Recalculate all stats after unequipping + newStats := recalculateAllStats(user, itemType, 0) + updates["attack"] = newStats.Attack + updates["defense"] = newStats.Defense + updates["strength"] = newStats.Strength + updates["dexterity"] = newStats.Dexterity + updates["max_hp"] = newStats.MaxHP + updates["max_mp"] = newStats.MaxMP + updates["exp_bonus"] = newStats.ExpBonus + updates["gold_bonus"] = newStats.GoldBonus + + return updates, nil +} + +// UserUnequipAccessory removes an accessory from a specific slot +func UserUnequipAccessory(user *users.User, slot int) (map[string]any, error) { + if slot < 1 || slot > 3 { + return nil, fmt.Errorf("invalid slot number: %d", slot) + } + + updates := make(map[string]any) + + // Check if slot has an item + switch slot { + case 1: + if user.Slot1ID == 0 { + return nil, fmt.Errorf("slot 1 is empty") + } + updates["slot_1_id"] = 0 + updates["slot_1_name"] = "" + case 2: + if user.Slot2ID == 0 { + return nil, fmt.Errorf("slot 2 is empty") + } + updates["slot_2_id"] = 0 + updates["slot_2_name"] = "" + case 3: + if user.Slot3ID == 0 { + return nil, fmt.Errorf("slot 3 is empty") + } + updates["slot_3_id"] = 0 + updates["slot_3_name"] = "" + } + + // Recalculate stats after removing accessory + newStats := recalculateAllStats(user, items.TypeAccessory, slot) + updates["attack"] = newStats.Attack + updates["defense"] = newStats.Defense + updates["strength"] = newStats.Strength + updates["dexterity"] = newStats.Dexterity + updates["max_hp"] = newStats.MaxHP + updates["max_mp"] = newStats.MaxMP + updates["exp_bonus"] = newStats.ExpBonus + updates["gold_bonus"] = newStats.GoldBonus + + return updates, nil +} + +// Stats represents calculated user stats +type Stats struct { + Attack int + Defense int + Strength int + Dexterity int + MaxHP int + MaxMP int + ExpBonus int + GoldBonus int +} + +// calculateNewStats calculates new stats when equipping an item +func calculateNewStats(user *users.User, newItem *items.Item) Stats { + stats := Stats{ + Attack: user.Attack, + Defense: user.Defense, + Strength: user.Strength, + Dexterity: user.Dexterity, + MaxHP: user.MaxHP, + MaxMP: user.MaxMP, + ExpBonus: user.ExpBonus, + GoldBonus: user.GoldBonus, + } + + // Remove old item stats if slot occupied + switch newItem.Type { + case items.TypeWeapon: + if user.WeaponID != 0 { + if oldItem, err := items.Find(user.WeaponID); err == nil { + removeItemStats(&stats, oldItem) + } + } + case items.TypeArmor: + if user.ArmorID != 0 { + if oldItem, err := items.Find(user.ArmorID); err == nil { + removeItemStats(&stats, oldItem) + } + } + case items.TypeShield: + if user.ShieldID != 0 { + if oldItem, err := items.Find(user.ShieldID); err == nil { + removeItemStats(&stats, oldItem) + } + } + } + + // Add new item stats + addItemStats(&stats, newItem) + return stats +} + +// calculateNewStatsForSlot calculates stats when equipping to a specific accessory slot +func calculateNewStatsForSlot(user *users.User, newItem *items.Item, slot int) Stats { + stats := Stats{ + Attack: user.Attack, + Defense: user.Defense, + Strength: user.Strength, + Dexterity: user.Dexterity, + MaxHP: user.MaxHP, + MaxMP: user.MaxMP, + ExpBonus: user.ExpBonus, + GoldBonus: user.GoldBonus, + } + + // Remove old accessory stats from the specified slot + var oldItemID int + switch slot { + case 1: + oldItemID = user.Slot1ID + case 2: + oldItemID = user.Slot2ID + case 3: + oldItemID = user.Slot3ID + } + + if oldItemID != 0 { + if oldItem, err := items.Find(oldItemID); err == nil { + removeItemStats(&stats, oldItem) + } + } + + // Add new item stats + addItemStats(&stats, newItem) + return stats +} + +// recalculateAllStats recalculates all stats from scratch, excluding specified item +func recalculateAllStats(user *users.User, excludeType, excludeSlot int) Stats { + // Start with base stats (these would come from class/level progression) + // For now, using current stats minus all equipment bonuses + stats := Stats{ + Attack: 0, // Base attack from strength/level + Defense: 0, // Base defense from dexterity/level + Strength: user.Strength, + Dexterity: user.Dexterity, + MaxHP: user.MaxHP, + MaxMP: user.MaxMP, + ExpBonus: 0, // Base exp bonus + GoldBonus: 0, // Base gold bonus + } + + // Add weapon stats (unless being removed) + if user.WeaponID != 0 && excludeType != items.TypeWeapon { + if item, err := items.Find(user.WeaponID); err == nil { + addItemStats(&stats, item) + } + } + + // Add armor stats (unless being removed) + if user.ArmorID != 0 && excludeType != items.TypeArmor { + if item, err := items.Find(user.ArmorID); err == nil { + addItemStats(&stats, item) + } + } + + // Add shield stats (unless being removed) + if user.ShieldID != 0 && excludeType != items.TypeShield { + if item, err := items.Find(user.ShieldID); err == nil { + addItemStats(&stats, item) + } + } + + // Add accessory stats (unless specific slot being removed) + if user.Slot1ID != 0 && !(excludeType == items.TypeAccessory && excludeSlot == 1) { + if item, err := items.Find(user.Slot1ID); err == nil { + addItemStats(&stats, item) + } + } + if user.Slot2ID != 0 && !(excludeType == items.TypeAccessory && excludeSlot == 2) { + if item, err := items.Find(user.Slot2ID); err == nil { + addItemStats(&stats, item) + } + } + if user.Slot3ID != 0 && !(excludeType == items.TypeAccessory && excludeSlot == 3) { + if item, err := items.Find(user.Slot3ID); err == nil { + addItemStats(&stats, item) + } + } + + return stats +} + +// addItemStats adds an item's stats to the current stats +func addItemStats(stats *Stats, item *items.Item) { + stats.Attack += item.Attack + stats.Defense += item.Defense + stats.Strength += item.Strength + stats.Dexterity += item.Dexterity + stats.MaxHP += item.MaxHP + stats.MaxMP += item.MaxMP + stats.ExpBonus += item.ExpBonus + stats.GoldBonus += item.GoldBonus +} + +// removeItemStats removes an item's stats from the current stats +func removeItemStats(stats *Stats, item *items.Item) { + stats.Attack -= item.Attack + stats.Defense -= item.Defense + stats.Strength -= item.Strength + stats.Dexterity -= item.Dexterity + stats.MaxHP -= item.MaxHP + stats.MaxMP -= item.MaxMP + stats.ExpBonus -= item.ExpBonus + stats.GoldBonus -= item.GoldBonus } diff --git a/internal/models/drops/drops.go b/internal/models/drops/drops.go deleted file mode 100644 index 59cd89e..0000000 --- a/internal/models/drops/drops.go +++ /dev/null @@ -1,101 +0,0 @@ -package drops - -import ( - "fmt" - - "dk/internal/database" -) - -// Drop represents a drop item in the game -type Drop struct { - ID int - Name string - Level int - Type int - Att string -} - -// DropType constants for drop types -const ( - TypeConsumable = 1 -) - -// New creates a new Drop with sensible defaults -func New() *Drop { - return &Drop{ - Name: "", - Level: 1, - Type: TypeConsumable, - Att: "", - } -} - -// Validate checks if drop has valid values -func (d *Drop) Validate() error { - if d.Name == "" { - return fmt.Errorf("drop name cannot be empty") - } - if d.Level < 1 { - return fmt.Errorf("drop Level must be at least 1") - } - if d.Type < TypeConsumable { - return fmt.Errorf("invalid drop type: %d", d.Type) - } - return nil -} - -// CRUD operations -func (d *Drop) Delete() error { - return database.Exec("DELETE FROM drops WHERE id = %d", d.ID) -} - -func (d *Drop) Insert() error { - id, err := database.Insert("drops", d, "ID") - if err != nil { - return err - } - d.ID = int(id) - return nil -} - -// Query functions -func Find(id int) (*Drop, error) { - var drop Drop - err := database.Get(&drop, "SELECT * FROM drops WHERE id = %d", id) - if err != nil { - return nil, fmt.Errorf("drop with ID %d not found", id) - } - return &drop, nil -} - -func All() ([]*Drop, error) { - var drops []*Drop - err := database.Select(&drops, "SELECT * FROM drops ORDER BY id ASC") - return drops, err -} - -func ByLevel(minLevel int) ([]*Drop, error) { - var drops []*Drop - err := database.Select(&drops, "SELECT * FROM drops WHERE level <= %d ORDER BY id ASC", minLevel) - return drops, err -} - -func ByType(dropType int) ([]*Drop, error) { - var drops []*Drop - err := database.Select(&drops, "SELECT * FROM drops WHERE type = %d ORDER BY id ASC", dropType) - return drops, err -} - -// Helper methods -func (d *Drop) IsConsumable() bool { - return d.Type == TypeConsumable -} - -func (d *Drop) TypeName() string { - switch d.Type { - case TypeConsumable: - return "Consumable" - default: - return "Unknown" - } -} diff --git a/internal/models/items/items.go b/internal/models/items/items.go index 7d11034..e306aad 100644 --- a/internal/models/items/items.go +++ b/internal/models/items/items.go @@ -8,29 +8,46 @@ import ( // Item represents an item in the game type Item struct { - ID int - Type int - Name string - Value int - Att int - Special string + ID int `db:"id"` + Type int `db:"type"` + Name string `db:"name"` + Lore string `db:"lore"` + Value int `db:"value"` + Attack int `db:"attack"` + Defense int `db:"defense"` + Strength int `db:"strength"` + Dexterity int `db:"dexterity"` + MaxHP int `db:"max_hp"` + MaxMP int `db:"max_mp"` + ExpBonus int `db:"exp_bonus"` + GoldBonus int `db:"gold_bonus"` + Special string `db:"special"` } // ItemType constants for item types const ( - TypeWeapon = 1 - TypeArmor = 2 - TypeShield = 3 + TypeWeapon = 1 + TypeArmor = 2 + TypeShield = 3 + TypeAccessory = 4 ) // New creates a new Item with sensible defaults func New() *Item { return &Item{ - Type: TypeWeapon, - Name: "", - Value: 0, - Att: 0, - Special: "", + Type: TypeWeapon, + Name: "", + Lore: "", + Value: 0, + Attack: 0, + Defense: 0, + Strength: 0, + Dexterity: 0, + MaxHP: 0, + MaxMP: 0, + ExpBonus: 0, + GoldBonus: 0, + Special: "", } } @@ -39,14 +56,11 @@ func (i *Item) Validate() error { if i.Name == "" { return fmt.Errorf("item name cannot be empty") } - if i.Type < TypeWeapon || i.Type > TypeShield { + if i.Type < TypeWeapon || i.Type > TypeAccessory { return fmt.Errorf("invalid item type: %d", i.Type) } if i.Value < 0 { - return fmt.Errorf("item Value cannot be negative") - } - if i.Att < 0 { - return fmt.Errorf("item Att cannot be negative") + return fmt.Errorf("item value cannot be negative") } return nil } @@ -57,7 +71,7 @@ func (i *Item) Delete() error { } func (i *Item) Insert() error { - id, err := database.Insert("items", i, "ID") + id, err := database.Insert("items", i, "id") if err != nil { return err } @@ -65,6 +79,25 @@ func (i *Item) Insert() error { return nil } +func (i *Item) Update() error { + fields := map[string]any{ + "type": i.Type, + "name": i.Name, + "lore": i.Lore, + "value": i.Value, + "attack": i.Attack, + "defense": i.Defense, + "strength": i.Strength, + "dexterity": i.Dexterity, + "max_hp": i.MaxHP, + "max_mp": i.MaxMP, + "exp_bonus": i.ExpBonus, + "gold_bonus": i.GoldBonus, + "special": i.Special, + } + return database.Update("items", fields, "id", i.ID) +} + // Query functions func Find(id int) (*Item, error) { var item Item @@ -77,13 +110,19 @@ func Find(id int) (*Item, error) { func All() ([]*Item, error) { var items []*Item - err := database.Select(&items, "SELECT * FROM items ORDER BY id ASC") + err := database.Select(&items, "SELECT * FROM items ORDER BY type ASC, value ASC, id ASC") return items, err } func ByType(itemType int) ([]*Item, error) { var items []*Item - err := database.Select(&items, "SELECT * FROM items WHERE type = %d ORDER BY id ASC", itemType) + err := database.Select(&items, "SELECT * FROM items WHERE type = %d ORDER BY value ASC, id ASC", itemType) + return items, err +} + +func ByValueRange(minValue, maxValue int) ([]*Item, error) { + var items []*Item + err := database.Select(&items, "SELECT * FROM items WHERE value >= %d AND value <= %d ORDER BY value ASC, id ASC", minValue, maxValue) return items, err } @@ -100,6 +139,10 @@ func (i *Item) IsShield() bool { return i.Type == TypeShield } +func (i *Item) IsAccessory() bool { + return i.Type == TypeAccessory +} + func (i *Item) TypeName() string { switch i.Type { case TypeWeapon: @@ -108,6 +151,8 @@ func (i *Item) TypeName() string { return "Armor" case TypeShield: return "Shield" + case TypeAccessory: + return "Accessory" default: return "Unknown" } @@ -118,5 +163,113 @@ func (i *Item) HasSpecial() bool { } func (i *Item) IsEquippable() bool { - return i.Type == TypeWeapon || i.Type == TypeArmor || i.Type == TypeShield + return i.Type >= TypeWeapon && i.Type <= TypeShield +} + +func (i *Item) IsSlottable() bool { + return i.Type == TypeAccessory +} + +func (i *Item) HasLore() bool { + return i.Lore != "" +} + +// Stat bonus methods +func (i *Item) HasAttackBonus() bool { + return i.Attack != 0 +} + +func (i *Item) HasDefenseBonus() bool { + return i.Defense != 0 +} + +func (i *Item) HasStrengthBonus() bool { + return i.Strength != 0 +} + +func (i *Item) HasDexterityBonus() bool { + return i.Dexterity != 0 +} + +func (i *Item) HasHPBonus() bool { + return i.MaxHP != 0 +} + +func (i *Item) HasMPBonus() bool { + return i.MaxMP != 0 +} + +func (i *Item) HasExpBonus() bool { + return i.ExpBonus != 0 +} + +func (i *Item) HasGoldBonus() bool { + return i.GoldBonus != 0 +} + +// Returns true if the item provides any stat bonuses +func (i *Item) HasStatBonuses() bool { + return i.Attack != 0 || i.Defense != 0 || i.Strength != 0 || + i.Dexterity != 0 || i.MaxHP != 0 || i.MaxMP != 0 || + i.ExpBonus != 0 || i.GoldBonus != 0 +} + +func (i *Item) GetStatBonuses() map[string]int { + bonuses := make(map[string]int) + + if i.Attack != 0 { + bonuses["Attack"] = i.Attack + } + if i.Defense != 0 { + bonuses["Defense"] = i.Defense + } + if i.Strength != 0 { + bonuses["Strength"] = i.Strength + } + if i.Dexterity != 0 { + bonuses["Dexterity"] = i.Dexterity + } + if i.MaxHP != 0 { + bonuses["Max HP"] = i.MaxHP + } + if i.MaxMP != 0 { + bonuses["Max MP"] = i.MaxMP + } + if i.ExpBonus != 0 { + bonuses["Exp Bonus"] = i.ExpBonus + } + if i.GoldBonus != 0 { + bonuses["Gold Bonus"] = i.GoldBonus + } + + return bonuses +} + +// GetPrimaryStatBonus returns the main stat bonus for the item type +func (i *Item) GetPrimaryStatBonus() int { + switch i.Type { + case TypeWeapon: + return i.Attack + case TypeArmor, TypeShield: + return i.Defense + case TypeAccessory: + // For accessories, return the highest stat bonus + max := 0 + stats := []int{i.Attack, i.Defense, i.Strength, i.Dexterity, i.MaxHP, i.MaxMP} + for _, stat := range stats { + if abs(stat) > abs(max) { + max = stat + } + } + return max + default: + return 0 + } +} + +func abs(x int) int { + if x < 0 { + return -x + } + return x } diff --git a/internal/routes/town.go b/internal/routes/town.go index d01f9f2..36bb31b 100644 --- a/internal/routes/town.go +++ b/internal/routes/town.go @@ -176,7 +176,12 @@ func buyItem(ctx sushi.Ctx) { } // Get equipment updates from actions - equipUpdates := actions.UserEquipItem(user, item) + equipUpdates, err := actions.UserEquipItem(user, item) + if err != nil { + sess.SetFlash("error", "Cannot equip item: "+err.Error()) + ctx.Redirect("/town/shop") + return + } err = database.Transaction(func() error { // Start with gold deduction diff --git a/sql/1_create_database.sql b/sql/1_create_database.sql index fb4487c..8ced3e2 100644 --- a/sql/1_create_database.sql +++ b/sql/1_create_database.sql @@ -63,50 +63,6 @@ CREATE TABLE forum ( `content` TEXT NOT NULL ); -DROP TABLE IF EXISTS items; -CREATE TABLE items ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT, - `type` INTEGER NOT NULL DEFAULT 1, - `name` TEXT NOT NULL, - `value` INTEGER NOT NULL DEFAULT 0, - `att` INTEGER NOT NULL DEFAULT 0, - `special` TEXT NOT NULL DEFAULT '' -); -INSERT INTO items VALUES -(1, 1, 'Stick', 10, 2, ''), -(2, 1, 'Branch', 30, 4, ''), -(3, 1, 'Club', 40, 5, ''), -(4, 1, 'Dagger', 90, 8, ''), -(5, 1, 'Hatchet', 150, 12, ''), -(6, 1, 'Axe', 200, 16, ''), -(7, 1, 'Brand', 300, 25, ''), -(8, 1, 'Poleaxe', 500, 35, ''), -(9, 1, 'Broadsword', 800, 45, ''), -(10, 1, 'Battle Axe', 1200, 50, ''), -(11, 1, 'Claymore', 2000, 60, ''), -(12, 1, 'Dark Axe', 3000, 100, 'expbonus,-5'), -(13, 1, 'Dark Sword', 4500, 125, 'expbonus,-10'), -(14, 1, 'Bright Sword', 6000, 100, 'expbonus,10'), -(15, 1, 'Magic Sword', 10000, 150, 'maxmp,50'), -(16, 1, 'Destiny Blade', 50000, 250, 'strength,50'), -(17, 2, 'Skivvies', 25, 2, 'goldbonus,10'), -(18, 2, 'Clothes', 50, 5, ''), -(19, 2, 'Leather Armor', 75, 10, ''), -(20, 2, 'Hard Leather Armor', 150, 25, ''), -(21, 2, 'Chain Mail', 300, 30, ''), -(22, 2, 'Bronze Plate', 900, 50, ''), -(23, 2, 'Iron Plate', 2000, 100, ''), -(24, 2, 'Magic Armor', 4000, 125, 'maxmp,50'), -(25, 2, 'Dark Armor', 5000, 150, 'expbonus,-10'), -(26, 2, 'Bright Armor', 10000, 175, 'expbonus,10'), -(27, 2, 'Destiny Raiment', 50000, 200, 'dexterity,50'), -(28, 3, 'Reed Shield', 50, 2, ''), -(29, 3, 'Buckler', 100, 4, ''), -(30, 3, 'Small Shield', 500, 10, ''), -(31, 3, 'Large Shield', 2500, 30, ''), -(32, 3, 'Silver Shield', 10000, 60, ''), -(33, 3, 'Destiny Aegis', 25000, 100, 'maxhp,50'); - DROP TABLE IF EXISTS classes; CREATE TABLE classes ( 'id' INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/sql/2_rework_items_system.sql b/sql/2_rework_items_system.sql new file mode 100644 index 0000000..03320de --- /dev/null +++ b/sql/2_rework_items_system.sql @@ -0,0 +1,88 @@ +-- Migration 2: rework items system +-- Created: 2025-08-25 10:50:50 + +DROP TABLE IF EXISTS drops; +DROP TABLE IF EXISTS items; + +CREATE TABLE items ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `type` INTEGER NOT NULL DEFAULT 1, + `name` TEXT NOT NULL, + `lore` TEXT DEFAULT '', + `value` INTEGER NOT NULL DEFAULT 0, + `attack` INTEGER NOT NULL DEFAULT 0, + `defense` INTEGER NOT NULL DEFAULT 0, + `strength` INTEGER NOT NULL DEFAULT 0, + `dexterity` INTEGER NOT NULL DEFAULT 0, + `max_hp` INTEGER NOT NULL DEFAULT 0, + `max_mp` INTEGER NOT NULL DEFAULT 0, + `exp_bonus` INTEGER NOT NULL DEFAULT 0, + `gold_bonus` INTEGER NOT NULL DEFAULT 0, + `special` TEXT NOT NULL DEFAULT '' +); +INSERT INTO items VALUES +(1, 1, 'Stick', 'Just a stick.', 10, 2, 0, 0, 0, 0, 0, 0, 0, ''), +(2, 1, 'Branch', 'Slightly larger stick.', 30, 4, 0, 0, 0, 0, 0, 0, 0, ''), +(3, 1, 'Club', 'Much larger stick.', 40, 5, 0, 0, 0, 0, 0, 0, 0, ''), +(4, 1, 'Dagger', 'Well-crafted knife.', 90, 8, 0, 0, 0, 0, 0, 0, 0, ''), +(5, 1, 'Hatchet', 'A smaller, sharpened axe.', 150, 12, 0, 0, 0, 0, 0, 0, 0, ''), +(6, 1, 'Axe', 'Full-sized, razor-sharp axe.', 200, 16, 0, 0, 0, 0, 0, 0, 0, ''), +(7, 1, 'Brand', 'An oddly-shaped, large knife.', 300, 25, 0, 0, 0, 0, 0, 0, 0, ''), +(8, 1, 'Poleaxe', 'A weighty axe on a long stick.', 500, 35, 0, 0, 0, 0, 0, 0, 0, ''), +(9, 1, 'Broadsword', 'Thick and heavy sword.', 800, 45, 0, 0, 0, 0, 0, 0, 0, ''), +(10, 1, 'Battle Axe', 'A large axe with spikes on the rear for catching armor.', 1200, 50, 0, 0, 0, 0, 0, 0, 0, ''), +(11, 1, 'Claymore', 'A very long sword made for cleaving through armor.', 2000, 60, 0, 0, 0, 0, 0, 0, 0, ''), +(12, 1, 'Dark Axe', 'Cursed axe with a dark aura - seems to be sharper than most, but strangely draining.', 3000, 100, 0, 0, 0, 0, 0, -5, 0, ''), +(13, 1, 'Dark Sword', 'Exceptionally sharp sword. The blade seems to shine with malice - you can feel it absorbing some of your will.', 4500, 125, 0, 0, 0, 0, 0, -10, 0, ''), +(14, 1, 'Bright Sword', 'The golden hilt and silver blade make this sword shine in the sun. Defeating enemies with it makes you feel fulfilled.', 6000, 100, 0, 0, 0, 0, 0, 10, 0, ''), +(15, 1, 'Magic Sword', 'One of Gilead''s mass-produced mage blades. Crafted with skill, enchanted by apprentices.', 10000, 150, 0, 0, 0, 0, 50, 0, 0, ''), +(16, 1, 'Destiny Blade', 'The sword that cleaves through demons like butter; a lost relic from humanity''s champion. Its immense strength is palpable.', 50000, 250, 0, 50, 0, 0, 0, 5, 5, ''), +(17, 2, 'Skivvies', 'Underwear! Plain and simple.', 25, 0, 2, 0, 0, 0, 0, 0, 10, ''), +(18, 2, 'Clothes', 'Standard working attire.', 50, 0, 5, 0, 0, 0, 0, 0, 0, ''), +(19, 2, 'Leather Armor', 'Basic protection provided by supple hide.', 75, 0, 10, 0, 0, 0, 0, 0, 0, ''), +(20, 2, 'Studded Armor', 'Leather armor enhanced with small metal buckles.', 150, 0, 25, 0, 0, 0, 0, 0, 0, ''), +(21, 2, 'Chain Mail', 'Iron links, pieced together to form a dense shirt.', 300, 0, 30, 0, 0, 0, 0, 0, 0, ''), +(22, 2, 'Bronze Plate', 'Pieces of bronze formed to your vital areas, secured with leather straps.', 900, 0, 50, 0, 0, 0, 0, 0, 0, ''), +(23, 2, 'Iron Plate', 'Stronger and slightly lighter than bronze plates.', 2000, 0, 100, 0, 0, 0, 0, 0, 0, ''), +(24, 2, 'Magic Armor', 'Steel armor crafted by the skilled artisans of Gilead, enchanted by apprentices.', 4000, 0, 125, 0, 0, 0, 50, 0, 0, ''), +(25, 2, 'Dark Armor', 'Armor that feels unnaturally heavy, although it also feels unnaturaly safe.', 5000, 0, 150, 0, 0, 0, 0, -10, 0, ''), +(26, 2, 'Bright Armor', 'These plates reflect the suns rays. You seem to understand your enemies attacks easier in it.', 10000, 0, 120, 0, 0, 0, 0, 10, 0, ''), +(27, 2, 'Destiny Raiment', 'A dazzling array of fabric and engineering layered over steel alloy. Despite the wars it has seen, it bears no scars.', 50000, 0, 200, 0, 50, 0, 0, 5, 5, ''), +(28, 3, 'Reed Shield', 'Stiff river reeds. Dried, cured, strapped together with twine.', 50, 0, 2, 0, 0, 0, 0, 0, 0, ''), +(29, 3, 'Buckler', 'Planks of wood cleaned up and formed into a small, round shield.', 100, 0, 4, 0, 0, 0, 0, 0, 0, ''), +(30, 3, 'Small Shield', 'Cedar is treated to withstand impacts, then bonded together with rivets.', 500, 0, 10, 0, 0, 0, 0, 0, 0, ''), +(31, 3, 'Tower Shield', 'A very heavy, tall shield built to withstand might blows.', 2500, 0, 30, 0, 0, 0, 0, 0, 0, ''), +(32, 3, 'Silver Shield', 'Surprisingly sturdy, this gleaming shield lets attacks bounce right off.', 10000, 0, 60, 0, 0, 0, 0, 0, 0, ''), +(33, 3, 'Destiny Aegis', 'An exquisite piece of forging, it is surprisingly light. Impacts seem to be absorbed into nothing.', 25000, 0, 100, 0, 0, 50, 0, 5, 5, ''), +(34, 4, 'Life Pebble', 'Shiny green rock. Seems to put a spring in your step.', 0, 0, 0, 0, 0, 10, 0, 0, 0, ''), +(35, 4, 'Life Stone', 'Glistening green rock. You feel energized with it.', 0, 0, 0, 0, 0, 25, 0, 0, 0, ''), +(36, 4, 'Life Rock', 'Polished green rock. You feel as if you''ll live longer with it.', 0, 0, 0, 0, 0, 50, 0, 0, 0, ''), +(37, 4, 'Magic Pebble', 'Shiny blue rock. You feel at ease with it.', 0, 0, 0, 0, 0, 0, 10, 0, 0, ''), +(38, 4, 'Magic Stone', 'Glistening blue rock. It feels like it gives you clarity.', 0, 0, 0, 0, 0, 0, 25, 0, 0, ''), +(39, 4, 'Magic Rock', 'Polished blue rock. It seems to hum in your mind.', 0, 0, 0, 0, 0, 0, 50, 0, 0, ''), +(40, 4, 'Dragon''s Scale', 'A scale from a young dragon.', 0, 0, 25, 0, 0, 0, 0, 0, 0, ''), +(41, 4, 'Dragon''s Plate', 'A piece of the ridge of an adult dragon.', 0, 0, 50, 0, 0, 0, 0, 0, 0, ''), +(42, 4, 'Dragon''s Claw', 'A claw taken from a dragon.', 0, 25, 0, 0, 0, 0, 0, 0, 0, ''), +(43, 4, 'Dragon''s Tooth', 'A fang ripped from a dragon.', 0, 50, 0, 0, 0, 0, 0, 0, 0, ''), +(44, 4, 'Dragon''s Tear', 'A sad reminder of a dragon''s death.', 0, 0, 0, 50, 0, 0, 0, 0, 0, ''), +(45, 4, 'Dragon''s Wing', 'A flap of leather from a dragon''s wing.', 0, 0, 0, 0, 50, 0, 0, 0, 0, ''), +(46, 4, 'Demon''s Sin', 'The demons sin; sacrificing life for power.', 0, 0, 0, 50, 0, -50, 0, 0, 0, ''), +(47, 4, 'Demon''s Fall', 'The demons failure; sacrificing wisdom for strength.', 0, 0, 0, 50, 0, 0, -50, 0, 0, ''), +(48, 4, 'Demon''s Lie', 'The demons lie; power is more valuable than life.', 0, 0, 0, 100, 0, -100, 0, 0, 0, ''), +(49, 4, 'Demon''s Hate', 'The demons hate; the will of others.', 0, 0, 0, 100, 0, 0, -100, 0, 0, ''), +(50, 4, 'Angel''s Joy', 'The angels joy; the celebration of life.', 0, 0, 0, 25, 0, 25, 0, 0, 0, ''), +(51, 4, 'Angel''s Rise', 'The angels rise; growth begets strength.', 0, 0, 0, 50, 0, 50, 0, 0, 0, ''), +(52, 4, 'Angel''s Truth', 'The angels truth; all breath is valuable.', 0, 0, 0, 75, 0, 75, 0, 0, 0, ''), +(53, 4, 'Angel''s Love', 'The angels love; the protection of existence.', 0, 0, 0, 100, 0, 100, 0, 0, 0, ''), +(54, 4, 'Seraph''s Wisdom', 'The seraphs wisdom; meekness is strength.', 0, 0, 0, 0, 25, 0, 25, 0, 0, ''), +(55, 4, 'Seraph''s Strength', 'The seraphs strength; sharpness of mind and soul.', 0, 0, 0, 0, 50, 0, 50, 0, 0, ''), +(56, 4, 'Seraph''s Creed', 'The seraphs creed; the lust for power makes weak.', 0, 0, 0, 0, 75, 0, 75, 0, 0, ''), +(57, 4, 'Seraph''s Loyalty', 'The seraphs loyalty; to all creation.', 0, 0, 0, 0, 100, 0, 100, 0, 0, ''), +(58, 4, 'Ruby', 'A gorgeous red crystal.', 0, 0, 0, 0, 0, 150, 0, 0, 0, ''), +(59, 4, 'Pearl', 'A beautiful white crystal.', 0, 0, 0, 0, 0, 0, 150, 0, 0, ''), +(60, 4, 'Emerald', 'A mesmerizing green crystal.', 0, 0, 0, 150, 0, 0, 0, 0, 0, ''), +(61, 4, 'Topaz', 'A translucent orange crystal.', 0, 0, 0, 0, 150, 0, 0, 0, 0, ''), +(62, 4, 'Obsidian', 'A razor-sharp black crystal.', 0, 150, 0, 0, 0, 0, 0, 0, 0, ''), +(63, 4, 'Diamond', 'A shockingly tough blue crystal.', 0, 0, 150, 0, 0, 0, 0, 0, 0, ''), +(64, 4, 'Memory Drop', 'This swirling blue liquid comes from the Fount.', 0, 0, 0, 0, 0, 0, 0, 10, 0, ''), +(65, 4, 'Fortune Drop', 'This mysterious green, sparkly concoction comes from lands unknown.', 0, 0, 0, 0, 0, 0, 0, 0, 10, ''); \ No newline at end of file diff --git a/templates/rightside.html b/templates/rightside.html index c6a7271..772239e 100644 --- a/templates/rightside.html +++ b/templates/rightside.html @@ -64,6 +64,12 @@

  • Dexterity: {user.Dexterity}
  • Attack: {user.Attack}
  • Defense: {user.Defense}
  • + {if user.ExpBonus != 0} +
  • EXP Bonus: {user.ExpBonus}%
  • + {/if} + {if user.GoldBonus != 0} +
  • Gold Bonus: {user.GoldBonus}%
  • + {/if} diff --git a/templates/town/shop.html b/templates/town/shop.html index 0209281..0e3ddaa 100644 --- a/templates/town/shop.html +++ b/templates/town/shop.html @@ -36,8 +36,13 @@ {/if} - {item.Att} - {if item.Type == 1}Attack{else}Defense{/if} + {if item.HasStatBonuses()} +
      + {for i,v in item.GetStatBonuses()} +
    • {i}: {v}{if i == "Gold Bonus" or i == "EXP Bonus"}%{/if}
    • + {/for} +
    + {/if} {if user.WeaponID == item.ID or user.ArmorID == item.ID or user.ShieldID == item.ID}