Skip to content

Latest commit

 

History

History
181 lines (146 loc) · 6.54 KB

2023-08-04.md

File metadata and controls

181 lines (146 loc) · 6.54 KB

2023-08-04

The game is "playable" (but really limited) since a few days now and it's time to move on new tough topics. I see a few "next steps" that I'm going to throw there in no particular order:

  • adding items on the floor and have the ability to pick them up / drop them down
  • map transitions (being able to go from map to map)
  • NPCs fighting back
  • merchants / stores
  • spells
  • consumables
  • infos on a given tile (name of the NPC if there is one, does he/it looks bad, ...)
  • map generation (later on probably)
  • "Fog of war" in dungeons

So I created the issues for all those features in gocastle/issues

I'll try to tackle the first one first because, let's be honest, inventory is a bit dull right now.

The first issue is to create an display items on the ground. Ideally, I'd like NPCs to go OVER then, and objects be display over tiles (or else we won't see anything). This was also working like this in "Castle of the winds" and since I'm taking my inspiration from it...

We I think about it, it's really close to the Avatar functions and maybe copying/tweaking them will be enough.

// Object represents an object with its properties.
type Object struct {
	Name        string        // Object name.
	Category    string        // Object category.
	Weight      int           // Object weight in grams
	InInventory bool          // Is Object in inventory
	Equipped    bool          // Is Object equipped
	PosX        int           // Object position
	PosY        int           // Object position
	Stats       []ObjectStat  // Object stats (e.g., strength, health, etc.).
	CanvasImage *canvas.Image // Object image
	CanvasPath  string        // Image path for Object
}

// DrawObject displays an object's image on the mapContainer
func (subject *Object) DrawObject(mapContainer *fyne.Container) {
	// don't put object in container is object is in inventory
	if !subject.InInventory {
		subject.CanvasImage.FillMode = canvas.ImageFillOriginal
		subject.CanvasImage.Resize(fyneTileSize)

		subject.MoveObject(subject.PosX, subject.PosY)

		mapContainer.Add(subject.CanvasImage)
	}
}

// MoveObject moves object's coordinates and updates image position on map
func (subject *Object) MoveObject(futurePosX int, futurePosY int) {
	// assign new values for subject position
	subject.PosX = futurePosX
	subject.PosY = futurePosY

	subject.CanvasImage.Move(fyne.NewPos(float32(futurePosX*tileSize), float32(futurePosY*tileSize)))
}

I basically copied the Avatar logic in the object logic (added InInventory, PosX|Y, CanvasImage|Path)

I also had modification to make to existing function like AddObjectToInventory

func (player *CharacterStats) AddObjectToInventory(obj Object, equip bool) int {
	player.Inventory = append(player.Inventory, obj)
	index := len(player.Inventory) - 1

	// TODO rework
	player.Inventory[index].InInventory = true

	if equip {
		player.EquipItem(index)
	}

	player.ComputeWeight()
	return index
}

Last thing I needed to make sure logic was sound was to create in game.go a knife and display it with DrawObject.

	knife2, err := model.CreateObject(model.HuntingKnife, 10, 10)
	if err != nil {
		err = fmt.Errorf("unable to create knife: %w", err)
		log.Fatalf("NewGame error: %s", err)
	}
	knife2.DrawObject(mapContainer)

And it kind of works...

First issue I have is that I can't manipulate it easily, especially if items go to/from inventory. I'll then have to make a ObjectsOnMap list like the NPC list to persist and manipulate them.

Second issue is that the knife is display on top of my character, which is something I feared. I have to make sure NPCs and characters are always on top. But that nice progress anyway :)

One trick is to keep a reference of the Avatar as an Object stored in the mapContainer.Objects, and remove/re-add the Avatar each time we move. This way, player and NPCs are always latest objects in mapContainer and thus on top.

type Avatar struct {
	CanvasImage          *canvas.Image
	CanvasPath           string
	PosX                 int
	PosY                 int
+ 	ObjectInMapContainer *fyne.CanvasObject
}
// MoveAvatar moves avatar's coordinates and updates image position on map
func (subject *Avatar) MoveAvatar(mapContainer *fyne.Container, futurePosX, futurePosY int) {
	// assign new values for subject position
	subject.PosX = futurePosX
	subject.PosY = futurePosY

	subject.CanvasImage.Move(fyne.NewPos(float32(futurePosX*tileSize), float32(futurePosY*tileSize)))

	// remove/re-add Avatar from mapContainer to redraw it on top
	mapContainer.Remove(*subject.ObjectInMapContainer)
	mapContainer.Add(*subject.ObjectInMapContainer)
}

Now that the 2nd problem is fixed, I can create a new attribute to the Map struct.

type Map struct {
	Name        string
	PlayerStart Coord
	spawnNPC    SpawnNPC
	NPCList     model.NPCsOnCurrentMap
+	ObjectList  []*model.Object
	MapMatrix   [][]int
}

Contrary to the NPCList (model.NPCsOnCurrentMap.List is a slice of NPCStats), I used a slice of model.Object pointers, which I hope will help me manipulate objects afterward. If it works well, I'll rework NPCList later.

Now, I can add my knife to this ObjectList in init.go and just loop on object list like for NPCs

// drawObjectList draws the "Objects on map" images on the mapContainer
func drawObjectList(mapContainer *fyne.Container) {
	// Loop through the ObjectList slice and create/draw each Object
	for _, object := range currentMap.ObjectList {
		object.DrawObject(mapContainer)
	}
}

A first trivial implementation on the "floor" part of the inventory screen may look like this

func displayFloorItems() (floorVBox *fyne.Container) {
	floorVBox = container.NewVBox()
	for _, item := range currentMap.ObjectList {
		if item.PosX == player.Avatar.PosX && item.PosY == player.Avatar.PosY {
			// item is on the same tile as player, display it in inventory
			currentItemContainer := container.NewVBox()
			nameLabel := widget.NewLabel(item.Name)
			takeButton := widget.NewButton("Take", func() {
				player.AddObjectToInventory(*item, false)
				// Remove object from currentMap ObjectList
				currentMap.FindObjectToRemove(item)
				// Remove current container as well from floor container
				floorVBox.Remove(currentItemContainer)
				// TODO Refresh items in inventory
			})
			detailsButton := widget.NewButton("Details", func() {
				// TODO display object statistics
			})
			currentItemContainer.Add(nameLabel)
			currentItemContainer.Add(container.NewGridWithColumns(2, takeButton, detailsButton))
			floorVBox.Add(currentItemContainer)
		}
	}
	return floorVBox
}