Go Adventure – Gear
Alright, today I added gear to the player. Now, we can go fight slimes with a stick. Gear is split into 3 categories, weapon, armor, and accessory.
SQL
In the database, the players table had to be modified to accept the item ID for each slot in the gear. Otherwise, the player would lose their gear every time they exited the game. Normally I would reference the gear to an item but item 0 not existing, as we are using it as a placeholder for an empty slot, if player has access to the database, they could modify the wrong gear in each slot.
ALTER TABLE players ADD COLUMN location_id INTEGER NOT NULL DEFAULT 1 REFERENCES locations(id), ADD COLUMN weapon_gear INTEGER NOT NULL DEFAULT 0, ADD COLUMN armor_gear INTEGER NOT NULL DEFAULT 0, ADD COLUMN accessory_gear INTEGER NOT NULL DEFAULT 0;
Go
In the player model, I added a new equipment struct that holds the weapon ID, armor ID and accessory ID. I also added the basic get functions for each slot.
type equipment struct {
weapon int32
armor int32
accessory int32
}
func (p *Player) GetWeapon() int32 {
return p.equipment.weapon
}
func (p *Player) GetArmor() int32 {
return p.equipment.armor
}
func (p *Player) GetAccessory() int32 {
return p.equipment.accessory
}
Now, for each gear slot, I had to create both an equip and un-equip function. The equip function has to do simple check such as, is the item ID valid, is the item category correct, is that item available in inventory, or is there something already equipped. These checks makes it that you cannot equip a boiled egg as a weapon. Silly silly. If something was already equipped, the item would be moved into the inventory. And then equips the new item and removes it from the inventory. No duplication cheat. On the other hand, the un-equip just checks if something is equipped. And then moves it to the inventory.
func (p *Player) EquipWeapon(itemList map[int32]*Item, weaponID int32) error {
// Check weapon ID is valid
if weaponID < 1 {
return errors.New("invalid weapon ID")
}
item, ok := itemList[weaponID]
if !ok {
return errors.New("invalid weapon ID")
}
if item.GetCategory() != "Weapon" {
return errors.New("weapon ID is not a weapon")
}
// Check if player has weapon in inventory
_, ok = p.GetInventory()[weaponID]
if !ok {
return errors.New("you do not have that weapon")
}
// Check if player already has a weapon equipped
_ = p.UnequipWeapon()
p.equipment.weapon = weaponID
_, err := p.RemoveItem(weaponID, 1)
if err != nil {
return err
}
return nil
}
func (p *Player) UnequipWeapon() error {
// Check weapon actually equipped
if p.GetWeapon() == 0 {
return errors.New("no weapon equipped")
}
// Move weapon into inventory
err := p.AddItem(p.GetWeapon(), 1)
if err != nil {
return err
}
p.equipment.weapon = 0
return nil
}
func (p *Player) EquipArmor(itemList map[int32]*Item, armorID int32) error {
// Check armor ID is valid
if armorID < 1 {
return errors.New("invalid armor ID")
}
item, ok := itemList[armorID]
if !ok {
return errors.New("invalid armor ID")
}
if item.GetCategory() != "Armor" {
return errors.New("armor ID is not an armor")
}
// Check if player has armor in inventory
_, ok = p.GetInventory()[armorID]
if !ok {
return errors.New("you do not have that armor")
}
// Check if player already has an armor equipped
_ = p.UnequipArmor()
p.equipment.armor = armorID
_, err := p.RemoveItem(armorID, 1)
if err != nil {
return err
}
return nil
}
func (p *Player) UnequipArmor() error {
// Check armor actually equipped
if p.GetArmor() == 0 {
return errors.New("no armor equipped")
}
// Move armor into inventory
err := p.AddItem(p.GetArmor(), 1)
if err != nil {
return err
}
p.equipment.armor = 0
return nil
}
func (p *Player) EquipAccessory(itemList map[int32]*Item, accessoryID int32) error {
// Check accessory ID is valid
if accessoryID < 1 {
return errors.New("invalid accessory ID")
}
item, ok := itemList[accessoryID]
if !ok {
return errors.New("invalid accessory ID")
}
if item.GetCategory() != "Accessory" {
return errors.New("accessory ID is not an accessory")
}
// Check if player has accessory in inventory
_, ok = p.GetInventory()[accessoryID]
if !ok {
return errors.New("you do not have that accessory")
}
// Check if player already has an accessory equipped
_ = p.UnequipAccessory()
p.equipment.accessory = accessoryID
_, err := p.RemoveItem(accessoryID, 1)
if err != nil {
return err
}
return nil
}
func (p *Player) UnequipAccessory() error {
// Check accessory actually equipped
if p.GetAccessory() == 0 {
return errors.New("no accessory equipped")
}
// Move accessory into inventory
err := p.AddItem(p.GetAccessory(), 1)
if err != nil {
return err
}
p.equipment.accessory = 0
return nil
}
Now, for the command, I created 3 of them. One to display the gear, one to equip the gear and one to un-equip the gear. Displaying the gear is easy, just display the gear. The un-equipping the gear was alright. Just need the player to specify the gear slot that needs to be un-equipped. Nothing too hard. Now for equipping gear, that one took a bit more trouble. Initially, I had the player enter the gear slot and item name to equip it. But, I thought it felt wrong. So, I modified it to only require the item name. Meaning that I had to modify the coding to check the item category before the item was equipped. This also acted as a check to see if the item is valid. In turn, this made the checks in the equip functions redundant. I will keep those checks though because it acts as an extra safeguard in case I made a mistake in the command.
case "gear":
Assets.Player.DisplayGear(Assets.Items)
return false, nil
case "equip":
if len(parts) < 2 {
return false, errors.New("equip what? 'usage: equip <item name>'")
}
itemName := strings.Join(parts[1:], " ")
var itemID int32
var found = false
for key, item := range Assets.Items {
if strings.ToLower(item.GetName()) == itemName {
itemID = key
found = true
break
}
}
if !found {
return false, errors.New("invalid item name")
}
switch Assets.Items[itemID].GetCategory() {
case "Weapon":
err := Assets.Player.EquipWeapon(Assets.Items, itemID)
if err != nil {
return false, err
}
case "Armor":
err := Assets.Player.EquipArmor(Assets.Items, itemID)
if err != nil {
return false, err
}
case "Accessory":
err := Assets.Player.EquipAccessory(Assets.Items, itemID)
if err != nil {
return false, err
}
default:
return false, errors.New("item is not an equipable item")
}
fmt.Printf("%s was equipped.\n", Assets.Items[itemID].GetName())
return false, nil
case "unequip":
if len(parts) < 2 {
return false, errors.New("un-equip what? 'usage: unequip <item slot>'")
}
switch parts[1] {
case "weapon":
err := Assets.Player.UnequipWeapon()
if err != nil {
return false, err
}
case "armor":
err := Assets.Player.UnequipArmor()
if err != nil {
return false, err
}
case "accessory":
err := Assets.Player.UnequipAccessory()
if err != nil {
return false, err
}
default:
return false, errors.New("invalid slot: valid inputs are 'weapon', 'armor' or 'accessory'")
}
fmt.Printf("You un-equipped your %s.\n", parts[1])
return false, nil
Now, with gear out of the way, I will be able to focus on the combat aspect of the game now. Finally, time to get some sweet loot from mobs.
So, with that, I’ll see you in the next one.
God bless.
