mirror of
https://github.com/mvvasilev/last_light.git
synced 2025-04-19 12:49:52 +03:00
small rewrite
This commit is contained in:
parent
6bcb59867e
commit
ec51edb7c0
77 changed files with 3407 additions and 2522 deletions
|
@ -114,6 +114,14 @@ func (s Size) Contains(x, y int) bool {
|
||||||
return 0 <= x && x < s.width && 0 <= y && y < s.height
|
return 0 <= x && x < s.width && 0 <= y && y < s.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LimitAdd(original, amount, limit int) int {
|
||||||
|
if original+amount > limit {
|
||||||
|
return limit
|
||||||
|
}
|
||||||
|
|
||||||
|
return original + amount
|
||||||
|
}
|
||||||
|
|
||||||
func LimitIncrement(i int, limit int) int {
|
func LimitIncrement(i int, limit int) int {
|
||||||
if (i + 1) > limit {
|
if (i + 1) > limit {
|
||||||
return i
|
return i
|
||||||
|
@ -122,6 +130,14 @@ func LimitIncrement(i int, limit int) int {
|
||||||
return i + 1
|
return i + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LimitSubtract(original, amount, limit int) int {
|
||||||
|
if original-amount < limit {
|
||||||
|
return limit
|
||||||
|
}
|
||||||
|
|
||||||
|
return original - amount
|
||||||
|
}
|
||||||
|
|
||||||
func LimitDecrement(i int, limit int) int {
|
func LimitDecrement(i int, limit int) int {
|
||||||
if (i - 1) < limit {
|
if (i - 1) < limit {
|
||||||
return i
|
return i
|
11
game/game.go
11
game/game.go
|
@ -2,16 +2,15 @@ package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
|
||||||
"mvvasilev/last_light/game/state"
|
"mvvasilev/last_light/game/state"
|
||||||
"mvvasilev/last_light/game/turns"
|
"mvvasilev/last_light/game/systems"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
turnSystem *turns.TurnSystem
|
turnSystem *systems.TurnSystem
|
||||||
inputSystem *input.InputSystem
|
inputSystem *systems.InputSystem
|
||||||
|
|
||||||
state state.GameState
|
state state.GameState
|
||||||
|
|
||||||
|
@ -21,9 +20,9 @@ type Game struct {
|
||||||
func CreateGame() *Game {
|
func CreateGame() *Game {
|
||||||
game := new(Game)
|
game := new(Game)
|
||||||
|
|
||||||
game.turnSystem = turns.CreateTurnSystem()
|
game.turnSystem = systems.CreateTurnSystem()
|
||||||
|
|
||||||
game.inputSystem = input.CreateInputSystemWithDefaultBindings()
|
game.inputSystem = systems.CreateInputSystemWithDefaultBindings()
|
||||||
|
|
||||||
game.state = state.CreateMainMenuState(game.turnSystem, game.inputSystem)
|
game.state = state.CreateMainMenuState(game.turnSystem, game.inputSystem)
|
||||||
|
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
package item
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Inventory interface {
|
|
||||||
Items() []Item
|
|
||||||
Shape() engine.Size
|
|
||||||
Push(item Item) bool
|
|
||||||
Drop(x, y int) Item
|
|
||||||
ItemAt(x, y int) Item
|
|
||||||
}
|
|
||||||
|
|
||||||
type BasicInventory struct {
|
|
||||||
contents []Item
|
|
||||||
shape engine.Size
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateInventory(shape engine.Size) *BasicInventory {
|
|
||||||
inv := new(BasicInventory)
|
|
||||||
|
|
||||||
inv.contents = make([]Item, 0, shape.Height()*shape.Width())
|
|
||||||
inv.shape = shape
|
|
||||||
|
|
||||||
return inv
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *BasicInventory) Items() (items []Item) {
|
|
||||||
return i.contents
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *BasicInventory) Shape() engine.Size {
|
|
||||||
return i.shape
|
|
||||||
}
|
|
||||||
|
|
||||||
func (inv *BasicInventory) Push(i Item) (success bool) {
|
|
||||||
if len(inv.contents) == inv.shape.Area() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
itemType := i.Type()
|
|
||||||
|
|
||||||
// Try to first find a matching item with capacity
|
|
||||||
for index, existingItem := range inv.contents {
|
|
||||||
if existingItem != nil && existingItem.Type().Id() == itemType.Id() {
|
|
||||||
if existingItem.Quantity()+1 > existingItem.Type().MaxStack() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
it := CreateBasicItem(itemType, existingItem.Quantity()+1)
|
|
||||||
inv.contents[index] = &it
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next, try to find an intermediate empty slot to fit this item into
|
|
||||||
for index, existingItem := range inv.contents {
|
|
||||||
if existingItem == nil {
|
|
||||||
inv.contents[index] = i
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, just append the new item at the end
|
|
||||||
inv.contents = append(inv.contents, i)
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *BasicInventory) Drop(x, y int) Item {
|
|
||||||
index := y*i.shape.Width() + x
|
|
||||||
|
|
||||||
if index > len(i.contents)-1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
item := i.contents[index]
|
|
||||||
|
|
||||||
i.contents[index] = nil
|
|
||||||
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *BasicInventory) ItemAt(x, y int) (item Item) {
|
|
||||||
index := y*i.shape.Width() + x
|
|
||||||
|
|
||||||
if index > len(i.contents)-1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.contents[index]
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
package item
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Item interface {
|
|
||||||
Name() (string, tcell.Style)
|
|
||||||
Description() string
|
|
||||||
Type() ItemType
|
|
||||||
Quantity() int
|
|
||||||
}
|
|
||||||
|
|
||||||
type BasicItem struct {
|
|
||||||
name string
|
|
||||||
nameStyle tcell.Style
|
|
||||||
description string
|
|
||||||
itemType ItemType
|
|
||||||
quantity int
|
|
||||||
}
|
|
||||||
|
|
||||||
func EmptyItem() BasicItem {
|
|
||||||
return BasicItem{
|
|
||||||
nameStyle: tcell.StyleDefault,
|
|
||||||
itemType: &BasicItemType{
|
|
||||||
name: "",
|
|
||||||
description: "",
|
|
||||||
tileIcon: ' ',
|
|
||||||
itemIcon: " ",
|
|
||||||
style: tcell.StyleDefault,
|
|
||||||
maxStack: 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateBasicItem(itemType ItemType, quantity int) BasicItem {
|
|
||||||
return BasicItem{
|
|
||||||
itemType: itemType,
|
|
||||||
quantity: quantity,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateBasicItemWithName(name string, style tcell.Style, itemType ItemType, quantity int) BasicItem {
|
|
||||||
return BasicItem{
|
|
||||||
name: name,
|
|
||||||
nameStyle: style,
|
|
||||||
itemType: itemType,
|
|
||||||
quantity: quantity,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i BasicItem) WithName(name string, style tcell.Style) BasicItem {
|
|
||||||
i.name = name
|
|
||||||
i.nameStyle = style
|
|
||||||
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i BasicItem) Name() (string, tcell.Style) {
|
|
||||||
if i.name == "" {
|
|
||||||
return i.itemType.Name(), i.nameStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.name, i.nameStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i BasicItem) Description() string {
|
|
||||||
if i.description == "" {
|
|
||||||
return i.itemType.Description()
|
|
||||||
}
|
|
||||||
|
|
||||||
return i.description
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i BasicItem) WithDescription(description string) BasicItem {
|
|
||||||
i.description = description
|
|
||||||
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i BasicItem) Type() ItemType {
|
|
||||||
return i.itemType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i BasicItem) Quantity() int {
|
|
||||||
return i.quantity
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
package item
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ItemType interface {
|
|
||||||
Id() int
|
|
||||||
Name() string
|
|
||||||
Description() string
|
|
||||||
TileIcon() rune
|
|
||||||
Icon() string
|
|
||||||
Style() tcell.Style
|
|
||||||
MaxStack() int
|
|
||||||
EquippableSlot() EquippedSlot
|
|
||||||
}
|
|
||||||
|
|
||||||
type BasicItemType struct {
|
|
||||||
id int
|
|
||||||
name string
|
|
||||||
description string
|
|
||||||
tileIcon rune
|
|
||||||
itemIcon string
|
|
||||||
maxStack int
|
|
||||||
equippableSlot EquippedSlot
|
|
||||||
|
|
||||||
style tcell.Style
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateBasicItemType(
|
|
||||||
id int,
|
|
||||||
name, description string,
|
|
||||||
tileIcon rune,
|
|
||||||
icon string,
|
|
||||||
maxStack int,
|
|
||||||
equippableSlot EquippedSlot,
|
|
||||||
style tcell.Style,
|
|
||||||
) *BasicItemType {
|
|
||||||
return &BasicItemType{
|
|
||||||
id: id,
|
|
||||||
name: name,
|
|
||||||
description: description,
|
|
||||||
tileIcon: tileIcon,
|
|
||||||
itemIcon: icon,
|
|
||||||
style: style,
|
|
||||||
maxStack: maxStack,
|
|
||||||
equippableSlot: equippableSlot,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *BasicItemType) Id() int {
|
|
||||||
return it.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *BasicItemType) Name() string {
|
|
||||||
return it.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *BasicItemType) Description() string {
|
|
||||||
return it.description
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *BasicItemType) TileIcon() rune {
|
|
||||||
return it.tileIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *BasicItemType) Icon() string {
|
|
||||||
return it.itemIcon
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *BasicItemType) Style() tcell.Style {
|
|
||||||
return it.style
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *BasicItemType) MaxStack() int {
|
|
||||||
return it.maxStack
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *BasicItemType) EquippableSlot() EquippedSlot {
|
|
||||||
return it.equippableSlot
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeFish() ItemType {
|
|
||||||
return &BasicItemType{
|
|
||||||
id: 0,
|
|
||||||
name: "Fish",
|
|
||||||
description: "What's a fish doing down here?",
|
|
||||||
tileIcon: '>',
|
|
||||||
itemIcon: "»o>",
|
|
||||||
style: tcell.StyleDefault.Foreground(tcell.ColorDarkCyan),
|
|
||||||
equippableSlot: EquippedSlotNone,
|
|
||||||
maxStack: 16,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeGold() ItemType {
|
|
||||||
return &BasicItemType{
|
|
||||||
id: 1,
|
|
||||||
name: "Gold",
|
|
||||||
description: "Not all those who wander are lost",
|
|
||||||
tileIcon: '¤',
|
|
||||||
itemIcon: " ¤ ",
|
|
||||||
equippableSlot: EquippedSlotNone,
|
|
||||||
style: tcell.StyleDefault.Foreground(tcell.ColorGoldenrod),
|
|
||||||
maxStack: 255,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeArrow() ItemType {
|
|
||||||
return &BasicItemType{
|
|
||||||
id: 2,
|
|
||||||
name: "Arrow",
|
|
||||||
description: "Ammunition for a bow",
|
|
||||||
tileIcon: '-',
|
|
||||||
itemIcon: "»->",
|
|
||||||
equippableSlot: EquippedSlotNone,
|
|
||||||
style: tcell.StyleDefault.Foreground(tcell.ColorGoldenrod),
|
|
||||||
maxStack: 32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeKey() ItemType {
|
|
||||||
return &BasicItemType{
|
|
||||||
id: 3,
|
|
||||||
name: "Key",
|
|
||||||
description: "Indispensable for unlocking things",
|
|
||||||
tileIcon: '¬',
|
|
||||||
itemIcon: " o╖",
|
|
||||||
equippableSlot: EquippedSlotNone,
|
|
||||||
style: tcell.StyleDefault.Foreground(tcell.ColorDarkGoldenrod),
|
|
||||||
maxStack: 1,
|
|
||||||
}
|
|
||||||
}
|
|
226
game/model/entity.go
Normal file
226
game/model/entity.go
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Direction int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DirectionNone Direction = iota
|
||||||
|
North
|
||||||
|
South
|
||||||
|
West
|
||||||
|
East
|
||||||
|
)
|
||||||
|
|
||||||
|
func DirectionName(dir Direction) string {
|
||||||
|
switch dir {
|
||||||
|
case North:
|
||||||
|
return "North"
|
||||||
|
case South:
|
||||||
|
return "South"
|
||||||
|
case West:
|
||||||
|
return "West"
|
||||||
|
case East:
|
||||||
|
return "East"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MovementDirectionOffset(dir Direction) (int, int) {
|
||||||
|
switch dir {
|
||||||
|
case North:
|
||||||
|
return 0, -1
|
||||||
|
case South:
|
||||||
|
return 0, 1
|
||||||
|
case West:
|
||||||
|
return -1, 0
|
||||||
|
case East:
|
||||||
|
return 1, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// type Entity interface {
|
||||||
|
// UniqueId() uuid.UUID
|
||||||
|
// Presentation() (rune, tcell.Style)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type MovableEntity interface {
|
||||||
|
// Position() engine.Position
|
||||||
|
// MoveTo(newPosition engine.Position)
|
||||||
|
|
||||||
|
// Entity
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type EquippedEntity interface {
|
||||||
|
// Inventory() *EquippedInventory
|
||||||
|
|
||||||
|
// Entity
|
||||||
|
// }
|
||||||
|
|
||||||
|
type Entity_NamedComponent struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entity_DescribedComponent struct {
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entity_PresentableComponent struct {
|
||||||
|
Rune rune
|
||||||
|
Style tcell.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entity_PositionedComponent struct {
|
||||||
|
Position engine.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entity_EquippedComponent struct {
|
||||||
|
Inventory *EquippedInventory
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entity_StatsHolderComponent struct {
|
||||||
|
BaseStats map[Stat]int
|
||||||
|
// StatModifiers []StatModifier
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entity_HealthComponent struct {
|
||||||
|
Health int
|
||||||
|
MaxHealth int
|
||||||
|
IsDead bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Entity_V2 interface {
|
||||||
|
UniqueId() uuid.UUID
|
||||||
|
|
||||||
|
Named() *Entity_NamedComponent
|
||||||
|
Described() *Entity_DescribedComponent
|
||||||
|
Presentable() *Entity_PresentableComponent
|
||||||
|
Positioned() *Entity_PositionedComponent
|
||||||
|
Equipped() *Entity_EquippedComponent
|
||||||
|
Stats() *Entity_StatsHolderComponent
|
||||||
|
HealthData() *Entity_HealthComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseEntity_V2 struct {
|
||||||
|
id uuid.UUID
|
||||||
|
|
||||||
|
named *Entity_NamedComponent
|
||||||
|
described *Entity_DescribedComponent
|
||||||
|
presentable *Entity_PresentableComponent
|
||||||
|
positioned *Entity_PositionedComponent
|
||||||
|
equipped *Entity_EquippedComponent
|
||||||
|
stats *Entity_StatsHolderComponent
|
||||||
|
damageable *Entity_HealthComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *BaseEntity_V2) UniqueId() uuid.UUID {
|
||||||
|
return be.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *BaseEntity_V2) Named() *Entity_NamedComponent {
|
||||||
|
return be.named
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *BaseEntity_V2) Described() *Entity_DescribedComponent {
|
||||||
|
return be.described
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *BaseEntity_V2) Presentable() *Entity_PresentableComponent {
|
||||||
|
return be.presentable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *BaseEntity_V2) Positioned() *Entity_PositionedComponent {
|
||||||
|
return be.positioned
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *BaseEntity_V2) Equipped() *Entity_EquippedComponent {
|
||||||
|
return be.equipped
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *BaseEntity_V2) Stats() *Entity_StatsHolderComponent {
|
||||||
|
return be.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
func (be *BaseEntity_V2) HealthData() *Entity_HealthComponent {
|
||||||
|
return be.damageable
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateEntity(components ...func(*BaseEntity_V2)) *BaseEntity_V2 {
|
||||||
|
e := &BaseEntity_V2{
|
||||||
|
id: uuid.New(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, comp := range components {
|
||||||
|
comp(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithName(name string) func(*BaseEntity_V2) {
|
||||||
|
return func(e *BaseEntity_V2) {
|
||||||
|
e.named = &Entity_NamedComponent{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithDescription(description string) func(e *BaseEntity_V2) {
|
||||||
|
return func(e *BaseEntity_V2) {
|
||||||
|
e.described = &Entity_DescribedComponent{
|
||||||
|
Description: description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithPresentation(symbol rune, style tcell.Style) func(e *BaseEntity_V2) {
|
||||||
|
return func(e *BaseEntity_V2) {
|
||||||
|
e.presentable = &Entity_PresentableComponent{
|
||||||
|
Rune: symbol,
|
||||||
|
Style: style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithPosition(pos engine.Position) func(e *BaseEntity_V2) {
|
||||||
|
return func(e *BaseEntity_V2) {
|
||||||
|
e.positioned = &Entity_PositionedComponent{
|
||||||
|
Position: pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithInventory(inv *EquippedInventory) func(e *BaseEntity_V2) {
|
||||||
|
return func(e *BaseEntity_V2) {
|
||||||
|
e.equipped = &Entity_EquippedComponent{
|
||||||
|
Inventory: inv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithStats(baseStats map[Stat]int, statModifiers ...StatModifier) func(e *BaseEntity_V2) {
|
||||||
|
return func(e *BaseEntity_V2) {
|
||||||
|
e.stats = &Entity_StatsHolderComponent{
|
||||||
|
BaseStats: baseStats,
|
||||||
|
// StatModifiers: statModifiers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithHealthData(health, maxHealth int, isDead bool) func(e *BaseEntity_V2) {
|
||||||
|
return func(e *BaseEntity_V2) {
|
||||||
|
e.damageable = &Entity_HealthComponent{
|
||||||
|
Health: health,
|
||||||
|
MaxHealth: maxHealth,
|
||||||
|
IsDead: isDead,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
game/model/entity_npc.go
Normal file
1
game/model/entity_npc.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package model
|
49
game/model/entity_player.go
Normal file
49
game/model/entity_player.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Player_V2 struct {
|
||||||
|
Entity_V2
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreatePlayer_V2(x, y int, playerBaseStats map[Stat]int) *Player_V2 {
|
||||||
|
p := &Player_V2{
|
||||||
|
Entity_V2: CreateEntity(
|
||||||
|
WithName("Player"),
|
||||||
|
WithPosition(engine.PositionAt(x, y)),
|
||||||
|
WithPresentation('@', tcell.StyleDefault),
|
||||||
|
WithInventory(CreateEquippedInventory()),
|
||||||
|
WithStats(playerBaseStats),
|
||||||
|
WithHealthData(0, 0, false),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.HealthData().MaxHealth = BaseMaxHealth(p)
|
||||||
|
p.HealthData().Health = p.HealthData().MaxHealth
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player_V2) Inventory() *EquippedInventory {
|
||||||
|
return p.Entity_V2.Equipped().Inventory
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player_V2) Position() engine.Position {
|
||||||
|
return p.Entity_V2.Positioned().Position
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player_V2) Presentation() (rune, tcell.Style) {
|
||||||
|
return p.Presentable().Rune, p.Presentable().Style
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player_V2) Stats() *Entity_StatsHolderComponent {
|
||||||
|
return p.Entity_V2.Stats()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Player_V2) HealthData() *Entity_HealthComponent {
|
||||||
|
return p.Entity_V2.HealthData()
|
||||||
|
}
|
57
game/model/entity_rpg_npc.go
Normal file
57
game/model/entity_rpg_npc.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// import (
|
||||||
|
// "mvvasilev/last_light/engine"
|
||||||
|
// "mvvasilev/last_light/game/item"
|
||||||
|
// "mvvasilev/last_light/game/rpg"
|
||||||
|
|
||||||
|
// "github.com/gdamore/tcell/v2"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// type RPGNPC interface {
|
||||||
|
// NPC
|
||||||
|
// rpg.RPGEntity
|
||||||
|
// EquippedEntity
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type BasicRPGNPC struct {
|
||||||
|
// inventory *item.EquippedInventory
|
||||||
|
|
||||||
|
// *BasicNPC
|
||||||
|
// *rpg.BasicRPGEntity
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateRPGNPC(x, y int, name string, representation rune, style tcell.Style, stats map[rpg.Stat]int) *BasicRPGNPC {
|
||||||
|
// rpgnpc := &BasicRPGNPC{
|
||||||
|
// inventory: item.CreateEquippedInventory(),
|
||||||
|
// BasicNPC: CreateNPC(
|
||||||
|
// engine.PositionAt(x, y),
|
||||||
|
// name,
|
||||||
|
// representation,
|
||||||
|
// style,
|
||||||
|
// ),
|
||||||
|
// BasicRPGEntity: rpg.CreateBasicRPGEntity(
|
||||||
|
// stats,
|
||||||
|
// map[rpg.Stat][]rpg.StatModifier{},
|
||||||
|
// ),
|
||||||
|
// }
|
||||||
|
|
||||||
|
// rpgnpc.Heal(rpg.BaseMaxHealth(rpgnpc))
|
||||||
|
|
||||||
|
// return rpgnpc
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (rnpc *BasicRPGNPC) Inventory() *item.EquippedInventory {
|
||||||
|
// return rnpc.inventory
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (p *BasicRPGNPC) CalculateAttack(other rpg.RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType rpg.DamageType) {
|
||||||
|
// mainHand := p.inventory.AtSlot(item.EquippedSlotDominantHand)
|
||||||
|
|
||||||
|
// switch mh := mainHand.(type) {
|
||||||
|
// case rpg.RPGItem:
|
||||||
|
// return rpg.PhysicalWeaponAttack(p, mh, other)
|
||||||
|
// default:
|
||||||
|
// return rpg.UnarmedAttack(p, other)
|
||||||
|
// }
|
||||||
|
// }
|
136
game/model/inventory.go
Normal file
136
game/model/inventory.go
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Inventory interface {
|
||||||
|
Items() []Item_V2
|
||||||
|
Shape() engine.Size
|
||||||
|
Push(item Item_V2) bool
|
||||||
|
Drop(x, y int) Item_V2
|
||||||
|
ItemAt(x, y int) Item_V2
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasicInventory struct {
|
||||||
|
contents []Item_V2
|
||||||
|
shape engine.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateInventory(shape engine.Size) *BasicInventory {
|
||||||
|
inv := new(BasicInventory)
|
||||||
|
|
||||||
|
inv.contents = make([]Item_V2, 0, shape.Height()*shape.Width())
|
||||||
|
inv.shape = shape
|
||||||
|
|
||||||
|
return inv
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BasicInventory) Items() (items []Item_V2) {
|
||||||
|
return i.contents
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BasicInventory) Shape() engine.Size {
|
||||||
|
return i.shape
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inv *BasicInventory) Push(i Item_V2) (success bool) {
|
||||||
|
if len(inv.contents) == inv.shape.Area() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
itemType := i.Type()
|
||||||
|
|
||||||
|
// Try to first find a matching item with capacity
|
||||||
|
for index, existingItem := range inv.contents {
|
||||||
|
if existingItem != nil && existingItem.Type() == itemType && existingItem.Quantifiable() != nil && i.Quantifiable() != nil {
|
||||||
|
// Cannot add even a single more item to this stack, skip it
|
||||||
|
if existingItem.Quantifiable().CurrentQuantity+1 > existingItem.Quantifiable().MaxQuantity {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item has capacity, but is less than total new item stack. Split between existing, and a new stack.
|
||||||
|
if existingItem.Quantifiable().CurrentQuantity+i.Quantifiable().CurrentQuantity > existingItem.Quantifiable().MaxQuantity {
|
||||||
|
// get difference in quantities
|
||||||
|
diff := existingItem.Quantifiable().MaxQuantity - existingItem.Quantifiable().CurrentQuantity
|
||||||
|
|
||||||
|
// set existing item quantity to max
|
||||||
|
existingItem.Quantifiable().CurrentQuantity = existingItem.Quantifiable().MaxQuantity
|
||||||
|
|
||||||
|
// set new item quantity to its current - diff
|
||||||
|
i.Quantifiable().CurrentQuantity -= i.Quantifiable().CurrentQuantity - diff
|
||||||
|
|
||||||
|
// Cannot pick up item, doing so would overflow the inventory
|
||||||
|
if index+1 >= inv.shape.Area() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
inv.contents[index+1] = i
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
inv.contents[index] = i
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next, try to find an intermediate empty slot to fit this item into
|
||||||
|
for index, existingItem := range inv.contents {
|
||||||
|
if existingItem == nil {
|
||||||
|
inv.contents[index] = i
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, just append the new item at the end
|
||||||
|
inv.contents = append(inv.contents, i)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BasicInventory) Drop(x, y int) Item_V2 {
|
||||||
|
index := y*i.shape.Width() + x
|
||||||
|
|
||||||
|
if index > len(i.contents)-1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
item := i.contents[index]
|
||||||
|
|
||||||
|
i.contents[index] = nil
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BasicInventory) ReduceQuantityAt(x, y int, amount int) {
|
||||||
|
it := i.ItemAt(x, y)
|
||||||
|
|
||||||
|
if it == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
quantityData := it.Quantifiable()
|
||||||
|
|
||||||
|
if quantityData != nil {
|
||||||
|
if quantityData.CurrentQuantity-amount <= 0 {
|
||||||
|
i.Drop(x, y)
|
||||||
|
} else {
|
||||||
|
quantityData.CurrentQuantity = quantityData.CurrentQuantity - amount
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i.Drop(x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BasicInventory) ItemAt(x, y int) (item Item_V2) {
|
||||||
|
index := y*i.shape.Width() + x
|
||||||
|
|
||||||
|
if index > len(i.contents)-1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.contents[index]
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package item
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
|
@ -18,13 +18,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type EquippedInventory struct {
|
type EquippedInventory struct {
|
||||||
offHand Item
|
offHand Item_V2
|
||||||
dominantHand Item
|
dominantHand Item_V2
|
||||||
|
|
||||||
head Item
|
head Item_V2
|
||||||
chestplate Item
|
chestplate Item_V2
|
||||||
leggings Item
|
leggings Item_V2
|
||||||
shoes Item
|
shoes Item_V2
|
||||||
|
|
||||||
*BasicInventory
|
*BasicInventory
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func CreateEquippedInventory() *EquippedInventory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ei *EquippedInventory) AtSlot(slot EquippedSlot) Item {
|
func (ei *EquippedInventory) AtSlot(slot EquippedSlot) Item_V2 {
|
||||||
switch slot {
|
switch slot {
|
||||||
case EquippedSlotOffhand:
|
case EquippedSlotOffhand:
|
||||||
return ei.offHand
|
return ei.offHand
|
||||||
|
@ -54,7 +54,7 @@ func (ei *EquippedInventory) AtSlot(slot EquippedSlot) Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ei *EquippedInventory) Equip(item Item, slot EquippedSlot) Item {
|
func (ei *EquippedInventory) Equip(item Item_V2, slot EquippedSlot) Item_V2 {
|
||||||
switch slot {
|
switch slot {
|
||||||
case EquippedSlotOffhand:
|
case EquippedSlotOffhand:
|
||||||
ei.offHand = item
|
ei.offHand = item
|
308
game/model/item.go
Normal file
308
game/model/item.go
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ItemMetaType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
MetaItemType_Physical_Weapon ItemMetaType = iota
|
||||||
|
MetaItemType_Magic_Weapon
|
||||||
|
MetaItemType_Weapon
|
||||||
|
MetaItemType_Physical_Armour
|
||||||
|
MetaItemType_Magic_Armour
|
||||||
|
MetaItemType_Armour
|
||||||
|
MetaItemType_Consumable
|
||||||
|
MetaItemType_Potion
|
||||||
|
)
|
||||||
|
|
||||||
|
type Item_V2 interface {
|
||||||
|
TileIcon() rune
|
||||||
|
Icon() string
|
||||||
|
Style() tcell.Style
|
||||||
|
Type() ItemType
|
||||||
|
|
||||||
|
Quantifiable() *Item_QuantifiableComponent
|
||||||
|
Usable() *Item_UsableComponent
|
||||||
|
Equippable() *Item_EquippableComponent
|
||||||
|
Named() *Item_NamedComponent
|
||||||
|
Described() *Item_DescribedComponent
|
||||||
|
Damaging() *Item_DamagingComponent
|
||||||
|
StatModifier() *Item_StatModifierComponent
|
||||||
|
MetaTypes() *Item_MetaTypesComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item_QuantifiableComponent struct {
|
||||||
|
MaxQuantity int
|
||||||
|
CurrentQuantity int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item_UsableComponent struct {
|
||||||
|
IsUsableBy func(Entity_V2) bool
|
||||||
|
Use func(*engine.GameEventLog, *Dungeon, Entity_V2)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item_EquippableComponent struct {
|
||||||
|
Slot EquippedSlot
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item_NamedComponent struct {
|
||||||
|
Name string
|
||||||
|
Style tcell.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item_DescribedComponent struct {
|
||||||
|
Description string
|
||||||
|
Style tcell.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item_DamagingComponent struct {
|
||||||
|
DamageRoll func() (damage int, dmgType DamageType)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item_StatModifierComponent struct {
|
||||||
|
StatModifiers []StatModifier
|
||||||
|
}
|
||||||
|
|
||||||
|
type Item_MetaTypesComponent struct {
|
||||||
|
Types []ItemMetaType
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseItem_V2 struct {
|
||||||
|
tileIcon rune
|
||||||
|
icon string
|
||||||
|
style tcell.Style
|
||||||
|
itemType ItemType
|
||||||
|
|
||||||
|
quantifiable *Item_QuantifiableComponent
|
||||||
|
usable *Item_UsableComponent
|
||||||
|
equippable *Item_EquippableComponent
|
||||||
|
named *Item_NamedComponent
|
||||||
|
described *Item_DescribedComponent
|
||||||
|
damaging *Item_DamagingComponent
|
||||||
|
statModifier *Item_StatModifierComponent
|
||||||
|
metaTypes *Item_MetaTypesComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) TileIcon() rune {
|
||||||
|
return i.tileIcon
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) Icon() string {
|
||||||
|
return i.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) Style() tcell.Style {
|
||||||
|
return i.style
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) Type() ItemType {
|
||||||
|
return i.itemType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) Quantifiable() *Item_QuantifiableComponent {
|
||||||
|
return i.quantifiable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) Usable() *Item_UsableComponent {
|
||||||
|
return i.usable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) Equippable() *Item_EquippableComponent {
|
||||||
|
return i.equippable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) Named() *Item_NamedComponent {
|
||||||
|
return i.named
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) Described() *Item_DescribedComponent {
|
||||||
|
return i.described
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) Damaging() *Item_DamagingComponent {
|
||||||
|
return i.damaging
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) StatModifier() *Item_StatModifierComponent {
|
||||||
|
return i.statModifier
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *BaseItem_V2) MetaTypes() *Item_MetaTypesComponent {
|
||||||
|
return i.metaTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
func createBaseItem(itemType ItemType, tileIcon rune, icon string, style tcell.Style, components ...func(*BaseItem_V2)) *BaseItem_V2 {
|
||||||
|
i := &BaseItem_V2{
|
||||||
|
itemType: itemType,
|
||||||
|
tileIcon: tileIcon,
|
||||||
|
icon: icon,
|
||||||
|
style: style,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, comp := range components {
|
||||||
|
comp(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func item_WithQuantity(quantity, maxQuantity int) func(*BaseItem_V2) {
|
||||||
|
return func(bi *BaseItem_V2) {
|
||||||
|
bi.quantifiable = &Item_QuantifiableComponent{
|
||||||
|
CurrentQuantity: quantity,
|
||||||
|
MaxQuantity: maxQuantity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func item_WithUsable(usabilityCheck func(Entity_V2) bool, useFunc func(*engine.GameEventLog, *Dungeon, Entity_V2)) func(*BaseItem_V2) {
|
||||||
|
return func(bi *BaseItem_V2) {
|
||||||
|
bi.usable = &Item_UsableComponent{
|
||||||
|
IsUsableBy: usabilityCheck,
|
||||||
|
Use: useFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func item_WithEquippable(slot EquippedSlot) func(*BaseItem_V2) {
|
||||||
|
return func(bi *BaseItem_V2) {
|
||||||
|
bi.equippable = &Item_EquippableComponent{
|
||||||
|
Slot: slot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func item_WithDamaging(damageFunc func() (damage int, dmgType DamageType)) func(*BaseItem_V2) {
|
||||||
|
return func(bi *BaseItem_V2) {
|
||||||
|
bi.damaging = &Item_DamagingComponent{
|
||||||
|
DamageRoll: damageFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func item_WithName(name string, style tcell.Style) func(*BaseItem_V2) {
|
||||||
|
return func(bi *BaseItem_V2) {
|
||||||
|
bi.named = &Item_NamedComponent{
|
||||||
|
Name: name,
|
||||||
|
Style: style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func item_WithDescription(description string, style tcell.Style) func(*BaseItem_V2) {
|
||||||
|
return func(bi *BaseItem_V2) {
|
||||||
|
bi.described = &Item_DescribedComponent{
|
||||||
|
Description: description,
|
||||||
|
Style: style,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func item_WithStatModifiers(statModifiers []StatModifier) func(*BaseItem_V2) {
|
||||||
|
return func(bi *BaseItem_V2) {
|
||||||
|
bi.statModifier = &Item_StatModifierComponent{
|
||||||
|
StatModifiers: statModifiers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func item_WithMetaTypes(metaTypes []ItemMetaType) func(*BaseItem_V2) {
|
||||||
|
return func(bi *BaseItem_V2) {
|
||||||
|
bi.metaTypes = &Item_MetaTypesComponent{
|
||||||
|
Types: metaTypes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// type Item interface {
|
||||||
|
// Name() (string, tcell.Style)
|
||||||
|
// Description() string
|
||||||
|
// Type() ItemType
|
||||||
|
// Quantity() int
|
||||||
|
// SetQuantity(quant int) Item
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type BasicItem struct {
|
||||||
|
// name string
|
||||||
|
// nameStyle tcell.Style
|
||||||
|
// description string
|
||||||
|
// itemType ItemType
|
||||||
|
// quantity int
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func EmptyItem() BasicItem {
|
||||||
|
// return BasicItem{
|
||||||
|
// nameStyle: tcell.StyleDefault,
|
||||||
|
// itemType: &BasicItemType{
|
||||||
|
// name: "",
|
||||||
|
// description: "",
|
||||||
|
// tileIcon: ' ',
|
||||||
|
// itemIcon: " ",
|
||||||
|
// style: tcell.StyleDefault,
|
||||||
|
// maxStack: 0,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateBasicItem(itemType ItemType, quantity int) BasicItem {
|
||||||
|
// return BasicItem{
|
||||||
|
// itemType: itemType,
|
||||||
|
// quantity: quantity,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateBasicItemWithName(name string, style tcell.Style, itemType ItemType, quantity int) BasicItem {
|
||||||
|
// return BasicItem{
|
||||||
|
// name: name,
|
||||||
|
// nameStyle: style,
|
||||||
|
// itemType: itemType,
|
||||||
|
// quantity: quantity,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (i BasicItem) WithName(name string, style tcell.Style) BasicItem {
|
||||||
|
// i.name = name
|
||||||
|
// i.nameStyle = style
|
||||||
|
|
||||||
|
// return i
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (i BasicItem) Name() (string, tcell.Style) {
|
||||||
|
// if i.name == "" {
|
||||||
|
// return i.itemType.Name(), i.nameStyle
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return i.name, i.nameStyle
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (i BasicItem) Description() string {
|
||||||
|
// if i.description == "" {
|
||||||
|
// return i.itemType.Description()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return i.description
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (i BasicItem) WithDescription(description string) BasicItem {
|
||||||
|
// i.description = description
|
||||||
|
|
||||||
|
// return i
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (i BasicItem) Type() ItemType {
|
||||||
|
// return i.itemType
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (i BasicItem) Quantity() int {
|
||||||
|
// return i.quantity
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (i BasicItem) SetQuantity(amount int) Item {
|
||||||
|
// i.quantity = i.quantity - amount
|
||||||
|
|
||||||
|
// return i
|
||||||
|
// }
|
486
game/model/items.go
Normal file
486
game/model/items.go
Normal file
|
@ -0,0 +1,486 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ItemType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Consumables
|
||||||
|
ItemType_Fish ItemType = iota
|
||||||
|
ItemType_SmallHealthPotion
|
||||||
|
ItemType_HealthPotion
|
||||||
|
ItemType_LargeHealthPotion
|
||||||
|
|
||||||
|
// Weapons
|
||||||
|
ItemType_Bow
|
||||||
|
ItemType_Longsword
|
||||||
|
ItemType_Club
|
||||||
|
ItemType_Dagger
|
||||||
|
ItemType_Handaxe
|
||||||
|
ItemType_Javelin
|
||||||
|
ItemType_LightHammer
|
||||||
|
ItemType_Mace
|
||||||
|
ItemType_Sickle
|
||||||
|
ItemType_Spear
|
||||||
|
ItemType_Quarterstaff
|
||||||
|
|
||||||
|
// Armour
|
||||||
|
|
||||||
|
// Special
|
||||||
|
)
|
||||||
|
|
||||||
|
func Item_Fish() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_Fish,
|
||||||
|
'>',
|
||||||
|
"»o>",
|
||||||
|
tcell.StyleDefault,
|
||||||
|
item_WithQuantity(1, 32),
|
||||||
|
item_WithName("Fish", tcell.StyleDefault),
|
||||||
|
item_WithDescription("On use heals for 1d4", tcell.StyleDefault),
|
||||||
|
item_WithUsable(
|
||||||
|
func(e Entity_V2) bool {
|
||||||
|
return e.HealthData() != nil
|
||||||
|
},
|
||||||
|
func(log *engine.GameEventLog, d *Dungeon, e Entity_V2) {
|
||||||
|
damageable := e.HealthData()
|
||||||
|
|
||||||
|
if damageable != nil {
|
||||||
|
healAmt := RollD4(1)
|
||||||
|
damageable.Health = engine.LimitAdd(damageable.Health, healAmt, damageable.MaxHealth)
|
||||||
|
|
||||||
|
name := "Entity"
|
||||||
|
|
||||||
|
if e.Named() != nil {
|
||||||
|
name = e.Named().Name
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Log(fmt.Sprintf("%s heals for %d HP", name, healAmt))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_SmallHealthPotion() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_SmallHealthPotion,
|
||||||
|
'ó',
|
||||||
|
" Ô ",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorRed),
|
||||||
|
item_WithQuantity(1, 3),
|
||||||
|
item_WithName("Small Health Potion", tcell.StyleDefault),
|
||||||
|
item_WithDescription("On use heals for 2d6", tcell.StyleDefault),
|
||||||
|
item_WithUsable(
|
||||||
|
func(e Entity_V2) bool {
|
||||||
|
return e.HealthData() != nil
|
||||||
|
},
|
||||||
|
func(log *engine.GameEventLog, d *Dungeon, e Entity_V2) {
|
||||||
|
damageable := e.HealthData()
|
||||||
|
|
||||||
|
if damageable != nil {
|
||||||
|
healAmt := RollD6(2)
|
||||||
|
damageable.Health = engine.LimitAdd(damageable.Health, healAmt, damageable.MaxHealth)
|
||||||
|
|
||||||
|
name := "Entity"
|
||||||
|
|
||||||
|
if e.Named() != nil {
|
||||||
|
name = e.Named().Name
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Log(fmt.Sprintf("%s heals for %d HP", name, healAmt))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_HealthPotion() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_HealthPotion,
|
||||||
|
'ó',
|
||||||
|
" Ô ",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorRed),
|
||||||
|
item_WithQuantity(1, 2),
|
||||||
|
item_WithName("Health Potion", tcell.StyleDefault),
|
||||||
|
item_WithDescription("On use heals for 3d6", tcell.StyleDefault),
|
||||||
|
item_WithUsable(
|
||||||
|
func(e Entity_V2) bool {
|
||||||
|
return e.HealthData() != nil
|
||||||
|
},
|
||||||
|
func(log *engine.GameEventLog, d *Dungeon, e Entity_V2) {
|
||||||
|
damageable := e.HealthData()
|
||||||
|
|
||||||
|
if damageable != nil {
|
||||||
|
healAmt := RollD6(3)
|
||||||
|
damageable.Health = engine.LimitAdd(damageable.Health, healAmt, damageable.MaxHealth)
|
||||||
|
|
||||||
|
name := "Entity"
|
||||||
|
|
||||||
|
if e.Named() != nil {
|
||||||
|
name = e.Named().Name
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Log(fmt.Sprintf("%s heals for %d HP", name, healAmt))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_LargeHealthPotion() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_LargeHealthPotion,
|
||||||
|
'ó',
|
||||||
|
" Ô ",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorRed),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Large Health Potion", tcell.StyleDefault),
|
||||||
|
item_WithDescription("On use heals for 4d6", tcell.StyleDefault),
|
||||||
|
item_WithUsable(
|
||||||
|
func(e Entity_V2) bool {
|
||||||
|
return e.HealthData() != nil
|
||||||
|
},
|
||||||
|
func(log *engine.GameEventLog, d *Dungeon, e Entity_V2) {
|
||||||
|
damageable := e.HealthData()
|
||||||
|
|
||||||
|
if damageable != nil {
|
||||||
|
healAmt := RollD6(4)
|
||||||
|
damageable.Health = engine.LimitAdd(damageable.Health, healAmt, damageable.MaxHealth)
|
||||||
|
|
||||||
|
name := "Entity"
|
||||||
|
|
||||||
|
if e.Named() != nil {
|
||||||
|
name = e.Named().Name
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Log(fmt.Sprintf("%s heals for %d HP", name, healAmt))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_Bow() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_Bow,
|
||||||
|
')',
|
||||||
|
" |)",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorBrown),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Bow", tcell.StyleDefault),
|
||||||
|
item_WithDescription("Deals 1d8 Piercing damage", tcell.StyleDefault),
|
||||||
|
item_WithDamaging(func() (damage int, dmgType DamageType) {
|
||||||
|
return RollD8(1), DamageType_Physical_Piercing
|
||||||
|
}),
|
||||||
|
item_WithMetaTypes([]ItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon}),
|
||||||
|
item_WithEquippable(EquippedSlotDominantHand),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_Longsword() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_Longsword,
|
||||||
|
'/',
|
||||||
|
"╪══",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Longsword", tcell.StyleDefault),
|
||||||
|
item_WithDescription("Deals 1d8 Slashing damage", tcell.StyleDefault),
|
||||||
|
item_WithDamaging(func() (damage int, dmgType DamageType) {
|
||||||
|
return RollD8(1), DamageType_Physical_Slashing
|
||||||
|
}),
|
||||||
|
item_WithMetaTypes([]ItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon}),
|
||||||
|
item_WithEquippable(EquippedSlotDominantHand),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_Club() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_Club,
|
||||||
|
'!',
|
||||||
|
"-══",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorSaddleBrown),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Club", tcell.StyleDefault),
|
||||||
|
item_WithDescription("Deals 1d8 Bludgeoning damage", tcell.StyleDefault),
|
||||||
|
item_WithDamaging(func() (damage int, dmgType DamageType) {
|
||||||
|
return RollD8(1), DamageType_Physical_Bludgeoning
|
||||||
|
}),
|
||||||
|
item_WithMetaTypes([]ItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon}),
|
||||||
|
item_WithEquippable(EquippedSlotDominantHand),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_Dagger() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_Dagger,
|
||||||
|
'-',
|
||||||
|
" +─",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Dagger", tcell.StyleDefault),
|
||||||
|
item_WithDescription("Deals 1d6 Piercing damage", tcell.StyleDefault),
|
||||||
|
item_WithDamaging(func() (damage int, dmgType DamageType) {
|
||||||
|
return RollD6(1), DamageType_Physical_Piercing
|
||||||
|
}),
|
||||||
|
item_WithMetaTypes([]ItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon}),
|
||||||
|
item_WithEquippable(EquippedSlotDominantHand),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_Handaxe() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_Handaxe,
|
||||||
|
'¶',
|
||||||
|
" ─╗",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Dagger", tcell.StyleDefault),
|
||||||
|
item_WithDescription("Deals 1d6 Slashing damage", tcell.StyleDefault),
|
||||||
|
item_WithDamaging(func() (damage int, dmgType DamageType) {
|
||||||
|
return RollD6(1), DamageType_Physical_Piercing
|
||||||
|
}),
|
||||||
|
item_WithMetaTypes([]ItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon}),
|
||||||
|
item_WithEquippable(EquippedSlotDominantHand),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_Javelin() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_Javelin,
|
||||||
|
'Î',
|
||||||
|
" ─>",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Javelin", tcell.StyleDefault),
|
||||||
|
item_WithDescription("Deals 1d6 Piercing damage", tcell.StyleDefault),
|
||||||
|
item_WithDamaging(func() (damage int, dmgType DamageType) {
|
||||||
|
return RollD6(1), DamageType_Physical_Piercing
|
||||||
|
}),
|
||||||
|
item_WithMetaTypes([]ItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon}),
|
||||||
|
item_WithEquippable(EquippedSlotDominantHand),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_LightHammer() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_LightHammer,
|
||||||
|
'i',
|
||||||
|
" ─0",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Light Hammer", tcell.StyleDefault),
|
||||||
|
item_WithDescription("Deals 1d6 Bludgeoning damage", tcell.StyleDefault),
|
||||||
|
item_WithDamaging(func() (damage int, dmgType DamageType) {
|
||||||
|
return RollD6(1), DamageType_Physical_Bludgeoning
|
||||||
|
}),
|
||||||
|
item_WithMetaTypes([]ItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon}),
|
||||||
|
item_WithEquippable(EquippedSlotDominantHand),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_Mace() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_Mace,
|
||||||
|
'i',
|
||||||
|
" ─¤",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Mace", tcell.StyleDefault),
|
||||||
|
item_WithDescription("Deals 1d6 Bludgeoning damage", tcell.StyleDefault),
|
||||||
|
item_WithDamaging(func() (damage int, dmgType DamageType) {
|
||||||
|
return RollD6(1), DamageType_Physical_Bludgeoning
|
||||||
|
}),
|
||||||
|
item_WithMetaTypes([]ItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon}),
|
||||||
|
item_WithEquippable(EquippedSlotDominantHand),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_Quarterstaff() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_Quarterstaff,
|
||||||
|
'|',
|
||||||
|
"───",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Quarterstaff", tcell.StyleDefault),
|
||||||
|
item_WithDescription("Deals 1d6 Bludgeoning damage", tcell.StyleDefault),
|
||||||
|
item_WithDamaging(func() (damage int, dmgType DamageType) {
|
||||||
|
return RollD6(1), DamageType_Physical_Bludgeoning
|
||||||
|
}),
|
||||||
|
item_WithMetaTypes([]ItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon}),
|
||||||
|
item_WithEquippable(EquippedSlotDominantHand),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_Sickle() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_Sickle,
|
||||||
|
'?',
|
||||||
|
" ─U",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Sickle", tcell.StyleDefault),
|
||||||
|
item_WithDescription("Deals 1d6 Slashing damage", tcell.StyleDefault),
|
||||||
|
item_WithDamaging(func() (damage int, dmgType DamageType) {
|
||||||
|
return RollD6(1), DamageType_Physical_Slashing
|
||||||
|
}),
|
||||||
|
item_WithMetaTypes([]ItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon}),
|
||||||
|
item_WithEquippable(EquippedSlotDominantHand),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Item_Spear() Item_V2 {
|
||||||
|
return createBaseItem(
|
||||||
|
ItemType_Spear,
|
||||||
|
'Î',
|
||||||
|
"──>",
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
||||||
|
item_WithQuantity(1, 1),
|
||||||
|
item_WithName("Spear", tcell.StyleDefault),
|
||||||
|
item_WithDescription("Deals 1d8 Piercing damage", tcell.StyleDefault),
|
||||||
|
item_WithDamaging(func() (damage int, dmgType DamageType) {
|
||||||
|
return RollD8(1), DamageType_Physical_Piercing
|
||||||
|
}),
|
||||||
|
item_WithMetaTypes([]ItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon}),
|
||||||
|
item_WithEquippable(EquippedSlotDominantHand),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// import (
|
||||||
|
// "github.com/gdamore/tcell/v2"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// type ItemType interface {
|
||||||
|
// Id() int
|
||||||
|
// Name() string
|
||||||
|
// Description() string
|
||||||
|
// TileIcon() rune
|
||||||
|
// Icon() string
|
||||||
|
// Style() tcell.Style
|
||||||
|
// MaxStack() int
|
||||||
|
// EquippableSlot() EquippedSlot
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type BasicItemType struct {
|
||||||
|
// id int
|
||||||
|
// name string
|
||||||
|
// description string
|
||||||
|
// tileIcon rune
|
||||||
|
// itemIcon string
|
||||||
|
// maxStack int
|
||||||
|
// equippableSlot EquippedSlot
|
||||||
|
|
||||||
|
// style tcell.Style
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateBasicItemType(
|
||||||
|
// id int,
|
||||||
|
// name, description string,
|
||||||
|
// tileIcon rune,
|
||||||
|
// icon string,
|
||||||
|
// maxStack int,
|
||||||
|
// equippableSlot EquippedSlot,
|
||||||
|
// style tcell.Style,
|
||||||
|
// ) *BasicItemType {
|
||||||
|
// return &BasicItemType{
|
||||||
|
// id: id,
|
||||||
|
// name: name,
|
||||||
|
// description: description,
|
||||||
|
// tileIcon: tileIcon,
|
||||||
|
// itemIcon: icon,
|
||||||
|
// style: style,
|
||||||
|
// maxStack: maxStack,
|
||||||
|
// equippableSlot: equippableSlot,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *BasicItemType) Id() int {
|
||||||
|
// return it.id
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *BasicItemType) Name() string {
|
||||||
|
// return it.name
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *BasicItemType) Description() string {
|
||||||
|
// return it.description
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *BasicItemType) TileIcon() rune {
|
||||||
|
// return it.tileIcon
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *BasicItemType) Icon() string {
|
||||||
|
// return it.itemIcon
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *BasicItemType) Style() tcell.Style {
|
||||||
|
// return it.style
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *BasicItemType) MaxStack() int {
|
||||||
|
// return it.maxStack
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *BasicItemType) EquippableSlot() EquippedSlot {
|
||||||
|
// return it.equippableSlot
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func ItemTypeFish() ItemType {
|
||||||
|
// return &BasicItemType{
|
||||||
|
// id: 0,
|
||||||
|
// name: "Fish",
|
||||||
|
// description: "What's a fish doing down here?",
|
||||||
|
// tileIcon: '>',
|
||||||
|
// itemIcon: "»o>",
|
||||||
|
// style: tcell.StyleDefault.Foreground(tcell.ColorDarkCyan),
|
||||||
|
// equippableSlot: EquippedSlotNone,
|
||||||
|
// maxStack: 16,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func ItemTypeGold() ItemType {
|
||||||
|
// return &BasicItemType{
|
||||||
|
// id: 1,
|
||||||
|
// name: "Gold",
|
||||||
|
// description: "Not all those who wander are lost",
|
||||||
|
// tileIcon: '¤',
|
||||||
|
// itemIcon: " ¤ ",
|
||||||
|
// equippableSlot: EquippedSlotNone,
|
||||||
|
// style: tcell.StyleDefault.Foreground(tcell.ColorGoldenrod),
|
||||||
|
// maxStack: 255,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func ItemTypeArrow() ItemType {
|
||||||
|
// return &BasicItemType{
|
||||||
|
// id: 2,
|
||||||
|
// name: "Arrow",
|
||||||
|
// description: "Ammunition for a bow",
|
||||||
|
// tileIcon: '-',
|
||||||
|
// itemIcon: "»->",
|
||||||
|
// equippableSlot: EquippedSlotNone,
|
||||||
|
// style: tcell.StyleDefault.Foreground(tcell.ColorGoldenrod),
|
||||||
|
// maxStack: 32,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func ItemTypeKey() ItemType {
|
||||||
|
// return &BasicItemType{
|
||||||
|
// id: 3,
|
||||||
|
// name: "Key",
|
||||||
|
// description: "Indispensable for unlocking things",
|
||||||
|
// tileIcon: '¬',
|
||||||
|
// itemIcon: " o╖",
|
||||||
|
// equippableSlot: EquippedSlotNone,
|
||||||
|
// style: tcell.StyleDefault.Foreground(tcell.ColorDarkGoldenrod),
|
||||||
|
// maxStack: 1,
|
||||||
|
// }
|
||||||
|
// }
|
116
game/model/rpg_entity.go
Normal file
116
game/model/rpg_entity.go
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// import "slices"
|
||||||
|
|
||||||
|
// type RPGEntity interface {
|
||||||
|
// BaseStat(stat Stat) int
|
||||||
|
// SetBaseStat(stat Stat, value int)
|
||||||
|
|
||||||
|
// CollectModifiersForStat(stat Stat) []StatModifier
|
||||||
|
// AddStatModifier(modifier StatModifier)
|
||||||
|
// RemoveStatModifier(id StatModifierId)
|
||||||
|
|
||||||
|
// IsDead() bool
|
||||||
|
// CurrentHealth() int
|
||||||
|
// Heal(health int)
|
||||||
|
// Damage(damage int)
|
||||||
|
|
||||||
|
// CalculateAttack(other RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type BasicRPGEntity struct {
|
||||||
|
// stats map[Stat]int
|
||||||
|
|
||||||
|
// statModifiers map[Stat][]StatModifier
|
||||||
|
|
||||||
|
// currentHealth int
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateBasicRPGEntity(baseStats map[Stat]int, statModifiers map[Stat][]StatModifier) *BasicRPGEntity {
|
||||||
|
// ent := &BasicRPGEntity{
|
||||||
|
// stats: baseStats,
|
||||||
|
// statModifiers: statModifiers,
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ent.currentHealth = BaseMaxHealth(ent)
|
||||||
|
|
||||||
|
// return ent
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (brpg *BasicRPGEntity) BaseStat(stat Stat) int {
|
||||||
|
// return brpg.stats[stat]
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (brpg *BasicRPGEntity) SetBaseStat(stat Stat, value int) {
|
||||||
|
// brpg.stats[stat] = value
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (brpg *BasicRPGEntity) CollectModifiersForStat(stat Stat) []StatModifier {
|
||||||
|
// modifiers := brpg.statModifiers[stat]
|
||||||
|
|
||||||
|
// if modifiers == nil {
|
||||||
|
// return []StatModifier{}
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return modifiers
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (brpg *BasicRPGEntity) AddStatModifier(modifier StatModifier) {
|
||||||
|
// existing := brpg.statModifiers[modifier.Stat]
|
||||||
|
|
||||||
|
// if existing == nil {
|
||||||
|
// existing = make([]StatModifier, 0)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// existing = append(existing, modifier)
|
||||||
|
|
||||||
|
// brpg.statModifiers[modifier.Stat] = existing
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (brpg *BasicRPGEntity) RemoveStatModifier(id StatModifierId) {
|
||||||
|
// for k, v := range brpg.statModifiers {
|
||||||
|
// for i, sm := range v {
|
||||||
|
// if sm.Id == id {
|
||||||
|
// brpg.statModifiers[k] = slices.Delete(v, i, i+1)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (brpg *BasicRPGEntity) CurrentHealth() int {
|
||||||
|
// return brpg.currentHealth
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (brpg *BasicRPGEntity) IsDead() bool {
|
||||||
|
// return brpg.CurrentHealth() <= 0
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (brpg *BasicRPGEntity) Heal(health int) {
|
||||||
|
// if brpg.IsDead() {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// maxHealth := BaseMaxHealth(brpg)
|
||||||
|
|
||||||
|
// if brpg.currentHealth+health > maxHealth {
|
||||||
|
// brpg.currentHealth = maxHealth
|
||||||
|
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// brpg.currentHealth += health
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (brpg *BasicRPGEntity) Damage(damage int) {
|
||||||
|
// if brpg.currentHealth-damage < 0 {
|
||||||
|
// brpg.currentHealth = 0
|
||||||
|
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// brpg.currentHealth -= damage
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (brpg *BasicRPGEntity) CalculateAttack(other RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType) {
|
||||||
|
// return UnarmedAttack(brpg, other)
|
||||||
|
// }
|
|
@ -1,9 +1,8 @@
|
||||||
package rpg
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/item"
|
|
||||||
"slices"
|
"slices"
|
||||||
"unicode"
|
"unicode"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
@ -12,11 +11,9 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: figure out event logging. Need to inform the player of the things that are happening...
|
|
||||||
|
|
||||||
const MaxNumberOfModifiers = 6
|
const MaxNumberOfModifiers = 6
|
||||||
|
|
||||||
type ItemSupplier func() item.Item
|
type ItemSupplier func() Item_V2
|
||||||
|
|
||||||
type LootTable struct {
|
type LootTable struct {
|
||||||
table []ItemSupplier
|
table []ItemSupplier
|
||||||
|
@ -34,7 +31,7 @@ func (igt *LootTable) Add(weight int, createItemFunction ItemSupplier) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (igt *LootTable) Generate() item.Item {
|
func (igt *LootTable) Generate() Item_V2 {
|
||||||
return igt.table[rand.Intn(len(igt.table))]()
|
return igt.table[rand.Intn(len(igt.table))]()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,25 +115,25 @@ func randomSuffix() string {
|
||||||
return suffixes[rand.Intn(len(suffixes))]
|
return suffixes[rand.Intn(len(suffixes))]
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateItemName(itemType RPGItemType, rarity ItemRarity) (string, tcell.Style) {
|
func generateItemName(existingItemName string, rarity ItemRarity) (string, tcell.Style) {
|
||||||
switch rarity {
|
switch rarity {
|
||||||
case ItemRarity_Common:
|
case ItemRarity_Common:
|
||||||
return itemType.Name(), tcell.StyleDefault
|
return existingItemName, tcell.StyleDefault
|
||||||
case ItemRarity_Uncommon:
|
case ItemRarity_Uncommon:
|
||||||
return randomAdjective() + " " + itemType.Name(), tcell.StyleDefault.Foreground(tcell.ColorLime)
|
return randomAdjective() + " " + existingItemName, tcell.StyleDefault.Foreground(tcell.ColorLime)
|
||||||
case ItemRarity_Rare:
|
case ItemRarity_Rare:
|
||||||
return itemType.Name() + " " + randomSuffix(), tcell.StyleDefault.Foreground(tcell.ColorBlue)
|
return existingItemName + " " + randomSuffix(), tcell.StyleDefault.Foreground(tcell.ColorBlue)
|
||||||
case ItemRarity_Epic:
|
case ItemRarity_Epic:
|
||||||
return randomAdjective() + " " + itemType.Name() + " " + randomSuffix(), tcell.StyleDefault.Foreground(tcell.ColorPurple)
|
return randomAdjective() + " " + existingItemName + " " + randomSuffix(), tcell.StyleDefault.Foreground(tcell.ColorPurple)
|
||||||
case ItemRarity_Legendary:
|
case ItemRarity_Legendary:
|
||||||
return generateUniqueItemName() + ", Legendary " + itemType.Name(), tcell.StyleDefault.Foreground(tcell.ColorOrange).Attributes(tcell.AttrBold)
|
return generateUniqueItemName() + ", Legendary " + existingItemName, tcell.StyleDefault.Foreground(tcell.ColorOrange).Attributes(tcell.AttrBold)
|
||||||
default:
|
default:
|
||||||
return itemType.Name(), tcell.StyleDefault
|
return existingItemName, tcell.StyleDefault
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func randomStat(metaItemTypes []RPGItemMetaType) Stat {
|
func randomStat(metaItemTypes []ItemMetaType) Stat {
|
||||||
stats := make(map[RPGItemMetaType][]Stat, 0)
|
stats := make(map[ItemMetaType][]Stat, 0)
|
||||||
|
|
||||||
stats[MetaItemType_Weapon] = []Stat{
|
stats[MetaItemType_Weapon] = []Stat{
|
||||||
Stat_Attributes_Strength,
|
Stat_Attributes_Strength,
|
||||||
|
@ -183,6 +180,12 @@ func randomStat(metaItemTypes []RPGItemMetaType) Stat {
|
||||||
Stat_DamageBonus_Magic_Thunder,
|
Stat_DamageBonus_Magic_Thunder,
|
||||||
Stat_DamageBonus_Magic_Acid,
|
Stat_DamageBonus_Magic_Acid,
|
||||||
Stat_DamageBonus_Magic_Poison,
|
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{
|
stats[MetaItemType_Physical_Armour] = []Stat{
|
||||||
|
@ -190,6 +193,10 @@ func randomStat(metaItemTypes []RPGItemMetaType) Stat {
|
||||||
Stat_DamageBonus_Physical_Slashing,
|
Stat_DamageBonus_Physical_Slashing,
|
||||||
Stat_DamageBonus_Physical_Piercing,
|
Stat_DamageBonus_Physical_Piercing,
|
||||||
Stat_DamageBonus_Physical_Bludgeoning,
|
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)
|
possibleStats := make([]Stat, 0, 10)
|
||||||
|
@ -201,7 +208,7 @@ func randomStat(metaItemTypes []RPGItemMetaType) Stat {
|
||||||
return slices.Compact(possibleStats)[rand.Intn(len(stats))]
|
return slices.Compact(possibleStats)[rand.Intn(len(stats))]
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateItemStatModifiers(itemType RPGItemType, rarity ItemRarity) []StatModifier {
|
func generateItemStatModifiers(itemMetaTypes []ItemMetaType, rarity ItemRarity) []StatModifier {
|
||||||
points := pointPerRarity(rarity)
|
points := pointPerRarity(rarity)
|
||||||
modifiers := make(map[Stat]*StatModifier, 0)
|
modifiers := make(map[Stat]*StatModifier, 0)
|
||||||
|
|
||||||
|
@ -218,7 +225,7 @@ func generateItemStatModifiers(itemType RPGItemType, rarity ItemRarity) []StatMo
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
stat := randomStat(itemType.MetaTypes())
|
stat := randomStat(itemMetaTypes)
|
||||||
|
|
||||||
existingForStat := modifiers[stat]
|
existingForStat := modifiers[stat]
|
||||||
|
|
||||||
|
@ -257,14 +264,32 @@ func generateItemStatModifiers(itemType RPGItemType, rarity ItemRarity) []StatMo
|
||||||
|
|
||||||
// Each rarity gets an amount of generation points, the higher the rarity, the more points
|
// 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.
|
// Each stat modifier consumes points. The higher the stat bonus, the more points it consumes.
|
||||||
func GenerateItemOfTypeAndRarity(itemType RPGItemType, rarity ItemRarity) RPGItem {
|
func GenerateItemOfTypeAndRarity(prototype Item_V2, rarity ItemRarity) Item_V2 {
|
||||||
// points := pointPerRarity(rarity)
|
if prototype.Named() == nil {
|
||||||
name, style := generateItemName(itemType, rarity)
|
return prototype
|
||||||
|
}
|
||||||
|
|
||||||
return CreateRPGItem(
|
if prototype.MetaTypes() == nil {
|
||||||
name,
|
return prototype
|
||||||
style,
|
}
|
||||||
itemType,
|
|
||||||
generateItemStatModifiers(itemType, rarity),
|
existingName := prototype.Named().Name
|
||||||
|
metaTypes := prototype.MetaTypes().Types
|
||||||
|
|
||||||
|
// points := pointPerRarity(rarity)
|
||||||
|
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().DamageRoll),
|
||||||
|
item_WithEquippable(prototype.Equippable().Slot),
|
||||||
|
item_WithStatModifiers(statModifiers),
|
||||||
|
item_WithMetaTypes(metaTypes),
|
||||||
)
|
)
|
||||||
}
|
}
|
73
game/model/rpg_items.go
Normal file
73
game/model/rpg_items.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// type RPGItemType interface {
|
||||||
|
// RollDamage(victim, attacker RPGEntity) (damage int, dmgType DamageType)
|
||||||
|
// Use(eventLogger *engine.GameEventLog, user RPGEntity)
|
||||||
|
// MetaTypes() []ItemMetaType
|
||||||
|
|
||||||
|
// item.ItemType
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type BasicRPGItemType struct {
|
||||||
|
// damageRollFunc func(victim, attacker RPGEntity) (damage int, dmgType DamageType)
|
||||||
|
// useFunc func(eventLogger *engine.GameEventLog, user RPGEntity)
|
||||||
|
|
||||||
|
// metaTypes []ItemMetaType
|
||||||
|
|
||||||
|
// *item.BasicItemType
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *BasicRPGItemType) Use(eventLogger *engine.GameEventLog, user RPGEntity) {
|
||||||
|
// if it.useFunc == nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// it.useFunc(eventLogger, user)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *BasicRPGItemType) RollDamage(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
||||||
|
// if it.damageRollFunc == nil {
|
||||||
|
// return 0, DamageType_Physical_Unarmed
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return it.damageRollFunc(victim, attacker)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *BasicRPGItemType) MetaTypes() []ItemMetaType {
|
||||||
|
// return it.metaTypes
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type RPGItem interface {
|
||||||
|
// Modifiers() []StatModifier
|
||||||
|
// RPGType() RPGItemType
|
||||||
|
|
||||||
|
// item.Item
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type BasicRPGItem struct {
|
||||||
|
// modifiers []StatModifier
|
||||||
|
// rpgType RPGItemType
|
||||||
|
|
||||||
|
// item.BasicItem
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (i *BasicRPGItem) Modifiers() []StatModifier {
|
||||||
|
// return i.modifiers
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (i *BasicRPGItem) RPGType() RPGItemType {
|
||||||
|
// return i.rpgType
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateRPGItem(name string, style tcell.Style, itemType RPGItemType, modifiers []StatModifier) RPGItem {
|
||||||
|
// return &BasicRPGItem{
|
||||||
|
// modifiers: modifiers,
|
||||||
|
// rpgType: itemType,
|
||||||
|
// BasicItem: item.CreateBasicItemWithName(
|
||||||
|
// name,
|
||||||
|
// style,
|
||||||
|
// itemType,
|
||||||
|
// 1,
|
||||||
|
// ),
|
||||||
|
// }
|
||||||
|
// }
|
|
@ -1,4 +1,4 @@
|
||||||
package rpg
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
@ -34,6 +34,18 @@ const (
|
||||||
Stat_DamageBonus_Magic_Acid Stat = 120
|
Stat_DamageBonus_Magic_Acid Stat = 120
|
||||||
Stat_DamageBonus_Magic_Poison Stat = 130
|
Stat_DamageBonus_Magic_Poison Stat = 130
|
||||||
|
|
||||||
|
Stat_ResistanceBonus_Physical_Unarmed Stat = 200
|
||||||
|
Stat_ResistanceBonus_Physical_Slashing Stat = 210
|
||||||
|
Stat_ResistanceBonus_Physical_Piercing Stat = 220
|
||||||
|
Stat_ResistanceBonus_Physical_Bludgeoning Stat = 230
|
||||||
|
|
||||||
|
Stat_ResistanceBonus_Magic_Fire Stat = 240
|
||||||
|
Stat_ResistanceBonus_Magic_Cold Stat = 250
|
||||||
|
Stat_ResistanceBonus_Magic_Necrotic Stat = 260
|
||||||
|
Stat_ResistanceBonus_Magic_Thunder Stat = 270
|
||||||
|
Stat_ResistanceBonus_Magic_Acid Stat = 280
|
||||||
|
Stat_ResistanceBonus_Magic_Poison Stat = 290
|
||||||
|
|
||||||
Stat_MaxHealthBonus Stat = 140
|
Stat_MaxHealthBonus Stat = 140
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -214,52 +226,99 @@ func DamageTypeToBonusStat(dmgType DamageType) Stat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DamageTypeToResistanceStat(dmgType DamageType) Stat {
|
||||||
|
switch dmgType {
|
||||||
|
case DamageType_Physical_Unarmed:
|
||||||
|
return Stat_ResistanceBonus_Physical_Unarmed
|
||||||
|
case DamageType_Physical_Slashing:
|
||||||
|
return Stat_ResistanceBonus_Physical_Slashing
|
||||||
|
case DamageType_Physical_Piercing:
|
||||||
|
return Stat_ResistanceBonus_Physical_Piercing
|
||||||
|
case DamageType_Physical_Bludgeoning:
|
||||||
|
return Stat_ResistanceBonus_Physical_Bludgeoning
|
||||||
|
case DamageType_Magic_Fire:
|
||||||
|
return Stat_ResistanceBonus_Magic_Fire
|
||||||
|
case DamageType_Magic_Cold:
|
||||||
|
return Stat_ResistanceBonus_Magic_Cold
|
||||||
|
case DamageType_Magic_Necrotic:
|
||||||
|
return Stat_ResistanceBonus_Magic_Necrotic
|
||||||
|
case DamageType_Magic_Thunder:
|
||||||
|
return Stat_ResistanceBonus_Magic_Thunder
|
||||||
|
case DamageType_Magic_Acid:
|
||||||
|
return Stat_ResistanceBonus_Magic_Acid
|
||||||
|
case DamageType_Magic_Poison:
|
||||||
|
return Stat_ResistanceBonus_Magic_Poison
|
||||||
|
default:
|
||||||
|
return Stat_NonExtant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func LuckRoll() int {
|
func LuckRoll() int {
|
||||||
return RollD10(1)
|
return RollD10(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TotalModifierForStat(entity RPGEntity, stat Stat) int {
|
func TotalModifierForStat(stats *Item_StatModifierComponent, stat Stat) int {
|
||||||
agg := 0
|
agg := 0
|
||||||
|
|
||||||
for _, m := range entity.CollectModifiersForStat(stat) {
|
for _, m := range stats.StatModifiers {
|
||||||
|
if m.Stat == stat {
|
||||||
agg += m.Bonus
|
agg += m.Bonus
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return agg
|
return agg
|
||||||
}
|
}
|
||||||
|
|
||||||
func StatValue(entity RPGEntity, stat Stat) int {
|
func statValue(stats *Entity_StatsHolderComponent, stat Stat) int {
|
||||||
return entity.BaseStat(stat) + TotalModifierForStat(entity, stat)
|
return stats.BaseStats[stat]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base Max Health is determined from constitution:
|
// Base Max Health is determined from constitution:
|
||||||
// 5*Constitution + Max Health Bonus
|
// 5*Constitution + Max Health Bonus
|
||||||
func BaseMaxHealth(entity RPGEntity) int {
|
func BaseMaxHealth(entity Entity_V2) int {
|
||||||
return 5*StatValue(entity, Stat_Attributes_Constitution) + StatValue(entity, Stat_MaxHealthBonus)
|
stats := entity.Stats()
|
||||||
|
|
||||||
|
if stats == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return 5*statValue(stats, Stat_Attributes_Constitution) + statValue(stats, Stat_MaxHealthBonus)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dexterity + Evasion bonus + luck roll
|
// Dexterity + Evasion bonus + luck roll
|
||||||
func EvasionRoll(victim RPGEntity) int {
|
func EvasionRoll(victim Entity_V2) int {
|
||||||
return StatValue(victim, Stat_Attributes_Dexterity) + StatValue(victim, Stat_EvasionBonus) + LuckRoll()
|
if victim.Stats() == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return statValue(victim.Stats(), Stat_Attributes_Dexterity) + statValue(victim.Stats(), Stat_EvasionBonus) + LuckRoll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strength + Precision bonus ( melee + total ) + luck roll
|
// Strength + Precision bonus ( melee + total ) + luck roll
|
||||||
func PhysicalPrecisionRoll(attacker RPGEntity) int {
|
func PhysicalPrecisionRoll(attacker Entity_V2) int {
|
||||||
return StatValue(attacker, Stat_Attributes_Strength) + StatValue(attacker, Stat_PhysicalPrecisionBonus) + StatValue(attacker, Stat_TotalPrecisionBonus) + LuckRoll()
|
if attacker.Stats() == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return statValue(attacker.Stats(), Stat_Attributes_Strength) + statValue(attacker.Stats(), Stat_PhysicalPrecisionBonus) + statValue(attacker.Stats(), Stat_TotalPrecisionBonus) + LuckRoll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intelligence + Precision bonus ( magic + total ) + luck roll
|
// Intelligence + Precision bonus ( magic + total ) + luck roll
|
||||||
func MagicPrecisionRoll(attacker RPGEntity) int {
|
func MagicPrecisionRoll(attacker Entity_V2) int {
|
||||||
return StatValue(attacker, Stat_Attributes_Intelligence) + StatValue(attacker, Stat_MagicPrecisionBonus) + StatValue(attacker, Stat_TotalPrecisionBonus) + LuckRoll()
|
if attacker.Stats() == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return statValue(attacker.Stats(), Stat_Attributes_Intelligence) + statValue(attacker.Stats(), Stat_MagicPrecisionBonus) + statValue(attacker.Stats(), Stat_TotalPrecisionBonus) + LuckRoll()
|
||||||
}
|
}
|
||||||
|
|
||||||
// true = hit lands, false = hit does not land
|
// true = hit lands, false = hit does not land
|
||||||
func MagicHitRoll(attacker RPGEntity, victim RPGEntity) bool {
|
func MagicHitRoll(attacker Entity_V2, victim Entity_V2) bool {
|
||||||
return hitRoll(EvasionRoll(victim), MagicPrecisionRoll(attacker))
|
return hitRoll(EvasionRoll(victim), MagicPrecisionRoll(attacker))
|
||||||
}
|
}
|
||||||
|
|
||||||
// true = hit lands, false = hit does not land
|
// true = hit lands, false = hit does not land
|
||||||
func PhysicalHitRoll(attacker RPGEntity, victim RPGEntity) (hit bool, evasion, precision int) {
|
func PhysicalHitRoll(attacker Entity_V2, victim Entity_V2) (hit bool, evasion, precision int) {
|
||||||
evasion = EvasionRoll(victim)
|
evasion = EvasionRoll(victim)
|
||||||
precision = PhysicalPrecisionRoll(attacker)
|
precision = PhysicalPrecisionRoll(attacker)
|
||||||
hit = hitRoll(evasion, precision)
|
hit = hitRoll(evasion, precision)
|
||||||
|
@ -271,21 +330,30 @@ func hitRoll(evasionRoll, precisionRoll int) bool {
|
||||||
return evasionRoll < precisionRoll
|
return evasionRoll < precisionRoll
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnarmedDamage(attacker RPGEntity) int {
|
func UnarmedDamage(attacker Entity_V2) int {
|
||||||
return RollD4(1) + StatValue(attacker, Stat_DamageBonus_Physical_Unarmed)
|
if attacker.Stats() == nil {
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func PhysicalWeaponDamange(attacker RPGEntity, weapon RPGItem, victim RPGEntity) (totalDamage int, dmgType DamageType) {
|
return RollD4(1) + statValue(attacker.Stats(), Stat_DamageBonus_Physical_Unarmed)
|
||||||
totalDamage, dmgType = weapon.RPGType().RollDamage()(victim, attacker)
|
}
|
||||||
|
|
||||||
|
func PhysicalWeaponDamage(attacker Entity_V2, weapon Item_V2, victim Entity_V2) (totalDamage int, dmgType DamageType) {
|
||||||
|
if attacker.Stats() == nil || weapon.Damaging() == nil || victim.Stats() == nil {
|
||||||
|
return 0, DamageType_Physical_Unarmed
|
||||||
|
}
|
||||||
|
|
||||||
|
totalDamage, dmgType = weapon.Damaging().DamageRoll()
|
||||||
|
|
||||||
bonusDmgStat := DamageTypeToBonusStat(dmgType)
|
bonusDmgStat := DamageTypeToBonusStat(dmgType)
|
||||||
|
dmgResistStat := DamageTypeToResistanceStat(dmgType)
|
||||||
|
|
||||||
totalDamage = totalDamage + StatValue(attacker, bonusDmgStat)
|
totalDamage = totalDamage + statValue(attacker.Stats(), bonusDmgStat) - statValue(victim.Stats(), dmgResistStat)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnarmedAttack(attacker RPGEntity, victim RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType) {
|
func UnarmedAttack(attacker Entity_V2, victim Entity_V2) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType) {
|
||||||
hit, evasionRoll, precisionRoll = PhysicalHitRoll(attacker, victim)
|
hit, evasionRoll, precisionRoll = PhysicalHitRoll(attacker, victim)
|
||||||
|
|
||||||
if !hit {
|
if !hit {
|
||||||
|
@ -298,14 +366,14 @@ func UnarmedAttack(attacker RPGEntity, victim RPGEntity) (hit bool, precisionRol
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func PhysicalWeaponAttack(attacker RPGEntity, weapon RPGItem, victim RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType) {
|
func PhysicalWeaponAttack(attacker Entity_V2, weapon Item_V2, victim Entity_V2) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType) {
|
||||||
hit, evasionRoll, precisionRoll = PhysicalHitRoll(attacker, victim)
|
hit, evasionRoll, precisionRoll = PhysicalHitRoll(attacker, victim)
|
||||||
|
|
||||||
if !hit {
|
if !hit {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
damage, damageType = PhysicalWeaponDamange(attacker, weapon, victim)
|
damage, damageType = PhysicalWeaponDamage(attacker, weapon, victim)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
328
game/model/world_dungeon.go
Normal file
328
game/model/world_dungeon.go
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DungeonType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DungeonTypeBSP DungeonType = iota
|
||||||
|
DungeonTypeCaverns
|
||||||
|
DungeonTypeMine
|
||||||
|
DungeonTypeUndercity
|
||||||
|
)
|
||||||
|
|
||||||
|
func randomDungeonType() DungeonType {
|
||||||
|
return DungeonType(rand.Intn(4))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Dungeon struct {
|
||||||
|
levels []*DungeonLevel
|
||||||
|
|
||||||
|
current int
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateDungeon(width, height int, depth int) *Dungeon {
|
||||||
|
levels := make([]*DungeonLevel, 0, depth)
|
||||||
|
|
||||||
|
for range depth {
|
||||||
|
levels = append(levels, CreateDungeonLevel(width, height, randomDungeonType()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Dungeon{
|
||||||
|
levels: levels,
|
||||||
|
current: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dungeon) CurrentLevel() *DungeonLevel {
|
||||||
|
return d.levels[d.current]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dungeon) MoveToNextLevel() (moved bool) {
|
||||||
|
if !d.HasNextLevel() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
d.current++
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dungeon) MoveToPreviousLevel() (moved bool) {
|
||||||
|
if !d.HasPreviousLevel() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
d.current--
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dungeon) NextLevel() *DungeonLevel {
|
||||||
|
if !d.HasNextLevel() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.levels[d.current+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dungeon) PreviousLevel() *DungeonLevel {
|
||||||
|
if !d.HasPreviousLevel() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.levels[d.current-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dungeon) HasPreviousLevel() bool {
|
||||||
|
return d.current-1 >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dungeon) HasNextLevel() bool {
|
||||||
|
return d.current+1 < len(d.levels)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DungeonLevel struct {
|
||||||
|
ground Map_V2
|
||||||
|
entitiesByPosition map[engine.Position]Entity_V2
|
||||||
|
entities map[uuid.UUID]Entity_V2
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateDungeonLevel(width, height int, dungeonType DungeonType) (dLevel *DungeonLevel) {
|
||||||
|
|
||||||
|
genTable := CreateLootTable()
|
||||||
|
|
||||||
|
genTable.Add(1, func() Item_V2 {
|
||||||
|
return Item_HealthPotion()
|
||||||
|
})
|
||||||
|
|
||||||
|
itemPool := []Item_V2{
|
||||||
|
Item_Bow(),
|
||||||
|
Item_Longsword(),
|
||||||
|
Item_Club(),
|
||||||
|
Item_Dagger(),
|
||||||
|
Item_Handaxe(),
|
||||||
|
Item_Javelin(),
|
||||||
|
Item_LightHammer(),
|
||||||
|
Item_Mace(),
|
||||||
|
Item_Quarterstaff(),
|
||||||
|
Item_Sickle(),
|
||||||
|
Item_Spear(),
|
||||||
|
}
|
||||||
|
|
||||||
|
genTable.Add(1, func() Item_V2 {
|
||||||
|
item := itemPool[rand.Intn(len(itemPool))]
|
||||||
|
|
||||||
|
rarities := []ItemRarity{
|
||||||
|
ItemRarity_Common,
|
||||||
|
ItemRarity_Uncommon,
|
||||||
|
ItemRarity_Rare,
|
||||||
|
ItemRarity_Epic,
|
||||||
|
ItemRarity_Legendary,
|
||||||
|
}
|
||||||
|
|
||||||
|
return GenerateItemOfTypeAndRarity(item, rarities[rand.Intn(len(rarities))])
|
||||||
|
})
|
||||||
|
|
||||||
|
var groundLevel Map_V2
|
||||||
|
|
||||||
|
switch dungeonType {
|
||||||
|
case DungeonTypeBSP:
|
||||||
|
groundLevel = CreateBSPDungeonMap(width, height, 4)
|
||||||
|
default:
|
||||||
|
groundLevel = CreateBSPDungeonMap(width, height, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
dLevel = &DungeonLevel{
|
||||||
|
ground: groundLevel,
|
||||||
|
entities: map[uuid.UUID]Entity_V2{},
|
||||||
|
entitiesByPosition: map[engine.Position]Entity_V2{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if groundLevel.Rooms() == nil {
|
||||||
|
return dLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
forbiddenItemPositions := make([]engine.Position, 0)
|
||||||
|
|
||||||
|
if groundLevel.NextLevelStaircase() != nil {
|
||||||
|
forbiddenItemPositions = append(forbiddenItemPositions, groundLevel.NextLevelStaircase().Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if groundLevel.PreviousLevelStaircase() != nil {
|
||||||
|
forbiddenItemPositions = append(forbiddenItemPositions, groundLevel.PreviousLevelStaircase().Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if groundLevel.PlayerSpawnPoint() != nil {
|
||||||
|
forbiddenItemPositions = append(forbiddenItemPositions, groundLevel.PreviousLevelStaircase().Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
items := SpawnItems(groundLevel.Rooms().Rooms, 0.01, genTable, forbiddenItemPositions)
|
||||||
|
|
||||||
|
for pos, it := range items {
|
||||||
|
tile := Map_TileAt(groundLevel, pos.X(), pos.Y())
|
||||||
|
|
||||||
|
if !tile.Passable() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
Map_SetTileAt(
|
||||||
|
groundLevel,
|
||||||
|
pos.X(),
|
||||||
|
pos.Y(),
|
||||||
|
CreateTileFromPrototype(tile, Tile_WithItem(it)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func SpawnItems(spawnableAreas []engine.BoundingBox, maxItemRatio float32, genTable *LootTable, forbiddenPositions []engine.Position) map[engine.Position]Item_V2 {
|
||||||
|
rooms := spawnableAreas
|
||||||
|
|
||||||
|
itemLocations := make(map[engine.Position]Item_V2, 0)
|
||||||
|
|
||||||
|
for _, r := range rooms {
|
||||||
|
maxItems := int(maxItemRatio * float32(r.Size().Area()))
|
||||||
|
|
||||||
|
if maxItems < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
numItems := rand.Intn(maxItems)
|
||||||
|
|
||||||
|
for range numItems {
|
||||||
|
item := genTable.Generate()
|
||||||
|
|
||||||
|
if item == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := engine.PositionAt(
|
||||||
|
engine.RandInt(r.Position().X()+1, r.Position().X()+r.Size().Width()-1),
|
||||||
|
engine.RandInt(r.Position().Y()+1, r.Position().Y()+r.Size().Height()-1),
|
||||||
|
)
|
||||||
|
|
||||||
|
if slices.Contains(forbiddenPositions, pos) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
itemLocations[pos] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemLocations
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) Ground() Map_V2 {
|
||||||
|
return d.ground
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) DropEntity(uuid uuid.UUID) {
|
||||||
|
ent := d.entities[uuid]
|
||||||
|
|
||||||
|
if ent != nil {
|
||||||
|
delete(d.entitiesByPosition, ent.Positioned().Position)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(d.entities, uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) AddEntity(entity Entity_V2) {
|
||||||
|
d.entities[entity.UniqueId()] = entity
|
||||||
|
|
||||||
|
if entity.Positioned() != nil {
|
||||||
|
d.entitiesByPosition[entity.Positioned().Position] = entity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) MoveEntityTo(uuid uuid.UUID, x, y int) {
|
||||||
|
ent := d.entities[uuid]
|
||||||
|
|
||||||
|
if ent == nil || ent.Positioned() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.RemoveEntityAt(ent.Positioned().Position.XY())
|
||||||
|
|
||||||
|
ent.Positioned().Position = engine.PositionAt(x, y)
|
||||||
|
|
||||||
|
d.entitiesByPosition[ent.Positioned().Position] = ent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) RemoveEntityAt(x, y int) {
|
||||||
|
delete(d.entitiesByPosition, engine.PositionAt(x, y))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) RemoveItemAt(x, y int) (item Item_V2) {
|
||||||
|
if !Map_IsInBounds(d.ground, x, y) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tile := Map_TileAt(d.ground, x, y)
|
||||||
|
|
||||||
|
if tile.Item() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
item = tile.Item().Item
|
||||||
|
|
||||||
|
tile.RemoveItem()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) SetItemAt(x, y int, it Item_V2) (success bool) {
|
||||||
|
if !d.TileAt(x, y).Passable() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tile := d.TileAt(x, y)
|
||||||
|
|
||||||
|
tile.WithItem(it)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) TileAt(x, y int) Tile_V2 {
|
||||||
|
entity := d.entitiesByPosition[engine.PositionAt(x, y)]
|
||||||
|
tile := Map_TileAt(d.ground, x, y)
|
||||||
|
|
||||||
|
if entity != nil {
|
||||||
|
return CreateTileFromPrototype(tile, Tile_WithEntity(entity))
|
||||||
|
}
|
||||||
|
|
||||||
|
return tile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) IsTilePassable(x, y int) bool {
|
||||||
|
if !Map_IsInBounds(d.ground, x, y) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tile := d.TileAt(x, y)
|
||||||
|
|
||||||
|
if tile.Entity() != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return tile.Passable()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) EntityAt(x, y int) (e Entity_V2) {
|
||||||
|
return d.entitiesByPosition[engine.PositionAt(x, y)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DungeonLevel) IsGroundTileOpaque(x, y int) bool {
|
||||||
|
if !Map_IsInBounds(d.ground, x, y) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return Map_TileAt(d.ground, x, y).Opaque()
|
||||||
|
}
|
50
game/model/world_empty_map.go
Normal file
50
game/model/world_empty_map.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// import "mvvasilev/last_light/engine"
|
||||||
|
|
||||||
|
// type EmptyDungeonMap struct {
|
||||||
|
// level *BasicMap
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (edl *EmptyDungeonMap) Size() engine.Size {
|
||||||
|
// return edl.level.Size()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (edl *EmptyDungeonMap) SetTileAt(x int, y int, t Tile) Tile {
|
||||||
|
// return edl.level.SetTileAt(x, y, t)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (edl *EmptyDungeonMap) TileAt(x int, y int) Tile {
|
||||||
|
// return edl.level.TileAt(x, y)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (edl *EmptyDungeonMap) IsInBounds(x, y int) bool {
|
||||||
|
// return edl.level.IsInBounds(x, y)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (edl *EmptyDungeonMap) Tick(dt int64) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (edl *EmptyDungeonMap) Rooms() []engine.BoundingBox {
|
||||||
|
// rooms := make([]engine.BoundingBox, 1)
|
||||||
|
|
||||||
|
// rooms = append(rooms, engine.BoundingBox{
|
||||||
|
// Sized: engine.WithSize(edl.Size()),
|
||||||
|
// Positioned: engine.WithPosition(engine.PositionAt(0, 0)),
|
||||||
|
// })
|
||||||
|
|
||||||
|
// return rooms
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (edl *EmptyDungeonMap) PlayerSpawnPoint() engine.Position {
|
||||||
|
// return engine.PositionAt(edl.Size().Width()/2, edl.Size().Height()/2)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (edl *EmptyDungeonMap) NextLevelStaircasePosition() engine.Position {
|
||||||
|
// return engine.PositionAt(edl.Size().Width()/3, edl.Size().Height()/3)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (bsp *EmptyDungeonMap) PreviousLevelStaircasePosition() engine.Position {
|
||||||
|
// return bsp.PlayerSpawnPoint()
|
||||||
|
// }
|
140
game/model/world_entity_map.go
Normal file
140
game/model/world_entity_map.go
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// import (
|
||||||
|
// "maps"
|
||||||
|
// "mvvasilev/last_light/engine"
|
||||||
|
// "mvvasilev/last_light/game/npc"
|
||||||
|
|
||||||
|
// "github.com/google/uuid"
|
||||||
|
// )
|
||||||
|
|
||||||
|
// type EntityMap struct {
|
||||||
|
// entities map[int]EntityTile
|
||||||
|
|
||||||
|
// engine.Sized
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateEntityMap(width, height int) *EntityMap {
|
||||||
|
// return &EntityMap{
|
||||||
|
// entities: make(map[int]EntityTile, 0),
|
||||||
|
// Sized: engine.WithSize(engine.SizeOf(width, height)),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) SetTileAt(x int, y int, t Tile) Tile {
|
||||||
|
// return nil
|
||||||
|
// // if !em.FitsWithin(x, y) {
|
||||||
|
// // return
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // index := em.Size().AsArrayIndex(x, y)
|
||||||
|
|
||||||
|
// // TODO? May not be necessary
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) FindEntityByUuid(uuid uuid.UUID) (key int, entity EntityTile) {
|
||||||
|
// for i, e := range em.entities {
|
||||||
|
// if e.Entity().UniqueId() == uuid {
|
||||||
|
// return i, e
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return -1, nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) AddEntity(entity Entity_V2) {
|
||||||
|
// if entity.Positioned() == nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if !em.FitsWithin(entity.Positioned().Position.XY()) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// key := em.Size().AsArrayIndex(entity.Positioned().Position.XY())
|
||||||
|
// et := CreateBasicEntityTile(entity)
|
||||||
|
|
||||||
|
// em.entities[key] = et
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) DropEntity(uuid uuid.UUID) {
|
||||||
|
// maps.DeleteFunc(em.entities, func(i int, et EntityTile) bool {
|
||||||
|
// return et.Entity().UniqueId() == uuid
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) MoveEntity(uuid uuid.UUID, dx, dy int) {
|
||||||
|
// oldKey, e := em.FindEntityByUuid(uuid)
|
||||||
|
|
||||||
|
// if e == nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if !em.FitsWithin(e.Entity().Positioned().Position.WithOffset(dx, dy).XY()) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// delete(em.entities, oldKey)
|
||||||
|
|
||||||
|
// newPos := e.Entity().Position().WithOffset(dx, dy)
|
||||||
|
// e.Entity().MoveTo(newPos)
|
||||||
|
|
||||||
|
// newKey := em.Size().AsArrayIndex(e.Entity().Position().XY())
|
||||||
|
|
||||||
|
// em.entities[newKey] = e
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) MoveEntityTo(uuid uuid.UUID, x, y int) {
|
||||||
|
// oldKey, e := em.FindEntityByUuid(uuid)
|
||||||
|
|
||||||
|
// if e == nil {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if !em.FitsWithin(x, y) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// delete(em.entities, oldKey)
|
||||||
|
|
||||||
|
// e.Entity().MoveTo(engine.PositionAt(x, y))
|
||||||
|
|
||||||
|
// newKey := em.Size().AsArrayIndex(e.Entity().Position().XY())
|
||||||
|
|
||||||
|
// em.entities[newKey] = e
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) TileAt(x int, y int) Tile {
|
||||||
|
// if !em.FitsWithin(x, y) {
|
||||||
|
// return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// key := em.Size().AsArrayIndex(x, y)
|
||||||
|
|
||||||
|
// return em.entities[key]
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) EntityAt(x, y int) (ent npc.MovableEntity) {
|
||||||
|
// tile := em.TileAt(x, y)
|
||||||
|
|
||||||
|
// if tile == nil {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return tile.(EntityTile).Entity()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) IsInBounds(x, y int) bool {
|
||||||
|
// return em.FitsWithin(x, y)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) MarkExplored(x, y int) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) ExploredTileAt(x, y int) Tile {
|
||||||
|
// return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (em *EntityMap) Tick(dt int64) {
|
||||||
|
// }
|
|
@ -1,4 +1,4 @@
|
||||||
package world
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
@ -27,7 +27,7 @@ type bspNode struct {
|
||||||
splitDir splitDirection
|
splitDir splitDirection
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateBSPDungeonMap(width, height int, numSplits int) *BSPDungeonMap {
|
func CreateBSPDungeonMap(width, height int, numSplits int) Map_V2 {
|
||||||
root := new(bspNode)
|
root := new(bspNode)
|
||||||
|
|
||||||
root.origin = engine.PositionAt(0, 0)
|
root.origin = engine.PositionAt(0, 0)
|
||||||
|
@ -35,10 +35,10 @@ func CreateBSPDungeonMap(width, height int, numSplits int) *BSPDungeonMap {
|
||||||
|
|
||||||
split(root, numSplits)
|
split(root, numSplits)
|
||||||
|
|
||||||
tiles := make([][]Tile, height)
|
tiles := make([][]Tile_V2, height)
|
||||||
|
|
||||||
for h := range height {
|
for h := range height {
|
||||||
tiles[h] = make([]Tile, width)
|
tiles[h] = make([]Tile_V2, width)
|
||||||
}
|
}
|
||||||
|
|
||||||
rooms := make([]engine.BoundingBox, 0, numSplits*numSplits)
|
rooms := make([]engine.BoundingBox, 0, numSplits*numSplits)
|
||||||
|
@ -88,28 +88,50 @@ func CreateBSPDungeonMap(width, height int, numSplits int) *BSPDungeonMap {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
bsp := new(BSPDungeonMap)
|
|
||||||
|
|
||||||
spawnRoom := findRoom(root.left)
|
spawnRoom := findRoom(root.left)
|
||||||
staircaseRoom := findRoom(root.right)
|
staircaseRoom := findRoom(root.right)
|
||||||
|
|
||||||
bsp.rooms = rooms
|
playerPos := engine.PositionAt(
|
||||||
bsp.level = CreateBasicMap(tiles, tcell.StyleDefault.Foreground(tcell.ColorLightSlateGrey))
|
|
||||||
|
|
||||||
bsp.playerSpawnPoint = engine.PositionAt(
|
|
||||||
spawnRoom.Position().X()+spawnRoom.Size().Width()/2,
|
spawnRoom.Position().X()+spawnRoom.Size().Width()/2,
|
||||||
spawnRoom.Position().Y()+spawnRoom.Size().Height()/2,
|
spawnRoom.Position().Y()+spawnRoom.Size().Height()/2,
|
||||||
)
|
)
|
||||||
|
|
||||||
bsp.nextLevelStaircase = engine.PositionAt(
|
newBsp := CreateMap(
|
||||||
|
root.size,
|
||||||
|
tiles,
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorLightSlateGrey),
|
||||||
|
Tile_Void(),
|
||||||
|
Map_WithRooms(rooms),
|
||||||
|
Map_WithPlayerSpawnPoint(playerPos),
|
||||||
|
Map_WithNextLevelStaircase(engine.PositionAt(
|
||||||
staircaseRoom.Position().X()+staircaseRoom.Size().Width()/2,
|
staircaseRoom.Position().X()+staircaseRoom.Size().Width()/2,
|
||||||
staircaseRoom.Position().Y()+staircaseRoom.Size().Height()/2,
|
staircaseRoom.Position().Y()+staircaseRoom.Size().Height()/2,
|
||||||
|
)),
|
||||||
|
Map_WithPreviousLevelStaircase(playerPos),
|
||||||
)
|
)
|
||||||
|
|
||||||
bsp.level.SetTileAt(bsp.nextLevelStaircase.X(), bsp.nextLevelStaircase.Y(), CreateStaticTile(bsp.nextLevelStaircase.X(), bsp.nextLevelStaircase.Y(), TileTypeStaircaseDown()))
|
Map_SetTileAt(newBsp, newBsp.NextLevelStaircase().Position.X(), newBsp.NextLevelStaircase().Position.Y(), Tile_StaircaseDown())
|
||||||
bsp.level.SetTileAt(bsp.playerSpawnPoint.X(), bsp.playerSpawnPoint.Y(), CreateStaticTile(bsp.playerSpawnPoint.X(), bsp.playerSpawnPoint.Y(), TileTypeStaircaseUp()))
|
Map_SetTileAt(newBsp, newBsp.PreviousLevelStaircase().Position.X(), newBsp.PreviousLevelStaircase().Position.Y(), Tile_StaircaseUp())
|
||||||
|
|
||||||
return bsp
|
// bsp := new(BSPDungeonMap)
|
||||||
|
|
||||||
|
// bsp.rooms = rooms
|
||||||
|
// bsp.level = CreateBasicMap(tiles, tcell.StyleDefault.Foreground(tcell.ColorLightSlateGrey))
|
||||||
|
|
||||||
|
// bsp.playerSpawnPoint = engine.PositionAt(
|
||||||
|
// spawnRoom.Position().X()+spawnRoom.Size().Width()/2,
|
||||||
|
// spawnRoom.Position().Y()+spawnRoom.Size().Height()/2,
|
||||||
|
// )
|
||||||
|
|
||||||
|
// bsp.nextLevelStaircase = engine.PositionAt(
|
||||||
|
// staircaseRoom.Position().X()+staircaseRoom.Size().Width()/2,
|
||||||
|
// staircaseRoom.Position().Y()+staircaseRoom.Size().Height()/2,
|
||||||
|
// )
|
||||||
|
|
||||||
|
// bsp.level.SetTileAt(bsp.nextLevelStaircase.X(), bsp.nextLevelStaircase.Y(), CreateStaticTile(bsp.nextLevelStaircase.X(), bsp.nextLevelStaircase.Y(), TileTypeStaircaseDown()))
|
||||||
|
// bsp.level.SetTileAt(bsp.playerSpawnPoint.X(), bsp.playerSpawnPoint.Y(), CreateStaticTile(bsp.playerSpawnPoint.X(), bsp.playerSpawnPoint.Y(), TileTypeStaircaseUp()))
|
||||||
|
|
||||||
|
return newBsp
|
||||||
}
|
}
|
||||||
|
|
||||||
func findRoom(parent *bspNode) engine.BoundingBox {
|
func findRoom(parent *bspNode) engine.BoundingBox {
|
||||||
|
@ -124,7 +146,7 @@ func findRoom(parent *bspNode) engine.BoundingBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func zCorridor(tiles [][]Tile, from engine.Position, to engine.Position, direction splitDirection) {
|
func zCorridor(tiles [][]Tile_V2, from engine.Position, to engine.Position, direction splitDirection) {
|
||||||
switch direction {
|
switch direction {
|
||||||
case splitDirectionHorizontal:
|
case splitDirectionHorizontal:
|
||||||
xMidPoint := (from.X() + to.X()) / 2
|
xMidPoint := (from.X() + to.X()) / 2
|
||||||
|
@ -206,7 +228,7 @@ func split(parent *bspNode, numSplits int) {
|
||||||
split(parent.right, numSplits-1)
|
split(parent.right, numSplits-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func horizontalTunnel(tiles [][]Tile, x1, x2, y int) {
|
func horizontalTunnel(tiles [][]Tile_V2, x1, x2, y int) {
|
||||||
if x1 > x2 {
|
if x1 > x2 {
|
||||||
tx := x2
|
tx := x2
|
||||||
x2 = x1
|
x2 = x1
|
||||||
|
@ -222,7 +244,7 @@ func horizontalTunnel(tiles [][]Tile, x1, x2, y int) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles[y][x] = CreateStaticTile(x, y, TileTypeGround())
|
tiles[y][x] = Tile_Ground()
|
||||||
|
|
||||||
placeWallAtIfNotPassable(tiles, x, y-1)
|
placeWallAtIfNotPassable(tiles, x, y-1)
|
||||||
placeWallAtIfNotPassable(tiles, x, y+1)
|
placeWallAtIfNotPassable(tiles, x, y+1)
|
||||||
|
@ -233,7 +255,7 @@ func horizontalTunnel(tiles [][]Tile, x1, x2, y int) {
|
||||||
placeWallAtIfNotPassable(tiles, x2, y+1)
|
placeWallAtIfNotPassable(tiles, x2, y+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func verticalTunnel(tiles [][]Tile, y1, y2, x int) {
|
func verticalTunnel(tiles [][]Tile_V2, y1, y2, x int) {
|
||||||
if y1 > y2 {
|
if y1 > y2 {
|
||||||
ty := y2
|
ty := y2
|
||||||
y2 = y1
|
y2 = y1
|
||||||
|
@ -249,7 +271,7 @@ func verticalTunnel(tiles [][]Tile, y1, y2, x int) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles[y][x] = CreateStaticTile(x, y, TileTypeGround())
|
tiles[y][x] = Tile_Ground()
|
||||||
|
|
||||||
placeWallAtIfNotPassable(tiles, x-1, y)
|
placeWallAtIfNotPassable(tiles, x-1, y)
|
||||||
placeWallAtIfNotPassable(tiles, x+1, y)
|
placeWallAtIfNotPassable(tiles, x+1, y)
|
||||||
|
@ -260,33 +282,33 @@ func verticalTunnel(tiles [][]Tile, y1, y2, x int) {
|
||||||
placeWallAtIfNotPassable(tiles, x+1, y2)
|
placeWallAtIfNotPassable(tiles, x+1, y2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func placeWallAtIfNotPassable(tiles [][]Tile, x, y int) {
|
func placeWallAtIfNotPassable(tiles [][]Tile_V2, x, y int) {
|
||||||
if tiles[y][x] != nil && tiles[y][x].Passable() {
|
if tiles[y][x] != nil && tiles[y][x].Passable() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tiles[y][x] = CreateStaticTile(x, y, TileTypeWall())
|
tiles[y][x] = Tile_Wall()
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeRoom(tiles [][]Tile, room engine.BoundingBox) {
|
func makeRoom(tiles [][]Tile_V2, room engine.BoundingBox) {
|
||||||
width := room.Size().Width()
|
width := room.Size().Width()
|
||||||
height := room.Size().Height()
|
height := room.Size().Height()
|
||||||
x := room.Position().X()
|
x := room.Position().X()
|
||||||
y := room.Position().Y()
|
y := room.Position().Y()
|
||||||
|
|
||||||
for w := x; w < x+width+1; w++ {
|
for w := x; w < x+width+1; w++ {
|
||||||
tiles[y][w] = CreateStaticTile(w, y, TileTypeWall())
|
tiles[y][w] = Tile_Wall()
|
||||||
tiles[y+height][w] = CreateStaticTile(w, y+height, TileTypeWall())
|
tiles[y+height][w] = Tile_Wall()
|
||||||
}
|
}
|
||||||
|
|
||||||
for h := y; h < y+height+1; h++ {
|
for h := y; h < y+height+1; h++ {
|
||||||
tiles[h][x] = CreateStaticTile(x, h, TileTypeWall())
|
tiles[h][x] = Tile_Wall()
|
||||||
tiles[h][x+width] = CreateStaticTile(x+width, h, TileTypeWall())
|
tiles[h][x+width] = Tile_Wall()
|
||||||
}
|
}
|
||||||
|
|
||||||
for h := y + 1; h < y+height; h++ {
|
for h := y + 1; h < y+height; h++ {
|
||||||
for w := x + 1; w < x+width; w++ {
|
for w := x + 1; w < x+width; w++ {
|
||||||
tiles[h][w] = CreateStaticTile(w, h, TileTypeGround())
|
tiles[h][w] = Tile_Ground()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
13
game/model/world_generate_empty_map.go
Normal file
13
game/model/world_generate_empty_map.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// import "github.com/gdamore/tcell/v2"
|
||||||
|
|
||||||
|
// func CreateEmptyDungeonLevel(width, height int) *BasicMap {
|
||||||
|
// tiles := make([][]Tile, height)
|
||||||
|
|
||||||
|
// for h := range height {
|
||||||
|
// tiles[h] = make([]Tile, width)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return CreateBasicMap(tiles, tcell.StyleDefault.Foreground(tcell.ColorLightSlateGrey))
|
||||||
|
// }
|
188
game/model/world_map.go
Normal file
188
game/model/world_map.go
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Map_V2 interface {
|
||||||
|
Size() engine.Size
|
||||||
|
Tiles() [][]Tile_V2
|
||||||
|
ExploredTiles() map[engine.Position]Tile_V2
|
||||||
|
ExploredTileStyle() tcell.Style
|
||||||
|
DefaultTile() Tile_V2
|
||||||
|
|
||||||
|
PlayerSpawnPoint() *Map_PlayerSpawnPointComponent
|
||||||
|
Rooms() *Map_RoomsComponent
|
||||||
|
NextLevelStaircase() *Map_NextLevelStaircaseComponent
|
||||||
|
PreviousLevelStaircase() *Map_PreviousLevelStaircaseComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
type Map_PlayerSpawnPointComponent struct {
|
||||||
|
Position engine.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
type Map_RoomsComponent struct {
|
||||||
|
Rooms []engine.BoundingBox
|
||||||
|
}
|
||||||
|
|
||||||
|
type Map_NextLevelStaircaseComponent struct {
|
||||||
|
Position engine.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
type Map_PreviousLevelStaircaseComponent struct {
|
||||||
|
Position engine.Position
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseMap struct {
|
||||||
|
size engine.Size
|
||||||
|
tiles [][]Tile_V2
|
||||||
|
exploredTiles map[engine.Position]Tile_V2
|
||||||
|
exploredStyle tcell.Style
|
||||||
|
defaultTile Tile_V2
|
||||||
|
|
||||||
|
playerSpawnPos *Map_PlayerSpawnPointComponent
|
||||||
|
rooms *Map_RoomsComponent
|
||||||
|
nextLevel *Map_NextLevelStaircaseComponent
|
||||||
|
prevLevel *Map_PreviousLevelStaircaseComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateMap(size engine.Size, tiles [][]Tile_V2, exploredStyle tcell.Style, defaultTile Tile_V2, components ...func(*BaseMap)) Map_V2 {
|
||||||
|
m := &BaseMap{
|
||||||
|
size: size,
|
||||||
|
tiles: tiles,
|
||||||
|
exploredTiles: make(map[engine.Position]Tile_V2, 0),
|
||||||
|
exploredStyle: exploredStyle,
|
||||||
|
defaultTile: defaultTile,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range components {
|
||||||
|
c(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BaseMap) Size() engine.Size {
|
||||||
|
return m.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BaseMap) Tiles() [][]Tile_V2 {
|
||||||
|
return m.tiles
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BaseMap) ExploredTiles() map[engine.Position]Tile_V2 {
|
||||||
|
return m.exploredTiles
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BaseMap) ExploredTileStyle() tcell.Style {
|
||||||
|
return m.exploredStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BaseMap) DefaultTile() Tile_V2 {
|
||||||
|
return m.defaultTile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BaseMap) PlayerSpawnPoint() *Map_PlayerSpawnPointComponent {
|
||||||
|
return m.playerSpawnPos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BaseMap) Rooms() *Map_RoomsComponent {
|
||||||
|
return m.rooms
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BaseMap) NextLevelStaircase() *Map_NextLevelStaircaseComponent {
|
||||||
|
return m.nextLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *BaseMap) PreviousLevelStaircase() *Map_PreviousLevelStaircaseComponent {
|
||||||
|
return m.prevLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map_WithRooms(rooms []engine.BoundingBox) func(*BaseMap) {
|
||||||
|
return func(bm *BaseMap) {
|
||||||
|
bm.rooms = &Map_RoomsComponent{
|
||||||
|
Rooms: rooms,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map_WithPlayerSpawnPoint(pos engine.Position) func(*BaseMap) {
|
||||||
|
return func(bm *BaseMap) {
|
||||||
|
bm.playerSpawnPos = &Map_PlayerSpawnPointComponent{
|
||||||
|
Position: pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map_WithNextLevelStaircase(pos engine.Position) func(*BaseMap) {
|
||||||
|
return func(bm *BaseMap) {
|
||||||
|
bm.nextLevel = &Map_NextLevelStaircaseComponent{
|
||||||
|
Position: pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map_WithPreviousLevelStaircase(pos engine.Position) func(*BaseMap) {
|
||||||
|
return func(bm *BaseMap) {
|
||||||
|
bm.prevLevel = &Map_PreviousLevelStaircaseComponent{
|
||||||
|
Position: pos,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map_SetTileAt(bm Map_V2, x int, y int, t Tile_V2) Tile_V2 {
|
||||||
|
if !Map_IsInBounds(bm, x, y) {
|
||||||
|
return bm.DefaultTile()
|
||||||
|
}
|
||||||
|
|
||||||
|
bm.Tiles()[y][x] = t
|
||||||
|
|
||||||
|
return bm.Tiles()[y][x]
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map_TileAt(bm Map_V2, x int, y int) Tile_V2 {
|
||||||
|
if !Map_IsInBounds(bm, x, y) {
|
||||||
|
return bm.DefaultTile()
|
||||||
|
}
|
||||||
|
|
||||||
|
tile := bm.Tiles()[y][x]
|
||||||
|
|
||||||
|
if tile == nil {
|
||||||
|
return bm.DefaultTile()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tile
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map_IsInBounds(bm Map_V2, x, y int) bool {
|
||||||
|
if x < 0 || y < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if x >= bm.Size().Width() || y >= bm.Size().Height() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map_ExploredTileAt(bm Map_V2, x, y int) Tile_V2 {
|
||||||
|
return bm.ExploredTiles()[engine.PositionAt(x, y)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map_MarkExplored(bm Map_V2, x, y int) {
|
||||||
|
if !Map_IsInBounds(bm, x, y) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tile := Map_TileAt(bm, x, y)
|
||||||
|
|
||||||
|
symbol, _ := tile.DefaultPresentation()
|
||||||
|
|
||||||
|
bm.ExploredTiles()[engine.PositionAt(x, y)] = &BaseTile{
|
||||||
|
defaultSymbol: symbol,
|
||||||
|
defaultStyle: bm.ExploredTileStyle(),
|
||||||
|
}
|
||||||
|
}
|
127
game/model/world_multilevel_map.go
Normal file
127
game/model/world_multilevel_map.go
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
// import "mvvasilev/last_light/engine"
|
||||||
|
|
||||||
|
// type MultilevelMap struct {
|
||||||
|
// layers []Map
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateMultilevelMap(maps ...Map) *MultilevelMap {
|
||||||
|
// m := new(MultilevelMap)
|
||||||
|
|
||||||
|
// m.layers = maps
|
||||||
|
|
||||||
|
// return m
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (mm *MultilevelMap) Size() engine.Size {
|
||||||
|
// if len(mm.layers) == 0 {
|
||||||
|
// return engine.SizeOf(0, 0)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return mm.layers[0].Size()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (mm *MultilevelMap) SetTileAt(x, y int, t Tile) Tile {
|
||||||
|
// return mm.layers[0].SetTileAt(x, y, t)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (mm *MultilevelMap) UnsetTileAtHeight(x, y, height int) {
|
||||||
|
// if len(mm.layers) < height {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// mm.layers[height].SetTileAt(x, y, nil)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (mm *MultilevelMap) SetTileAtHeight(x, y, height int, t Tile) {
|
||||||
|
// if len(mm.layers) < height {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
|
||||||
|
// mm.layers[height].SetTileAt(x, y, t)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (mm *MultilevelMap) CollectTilesAt(x, y int, filter func(t Tile) bool) []Tile {
|
||||||
|
// tiles := make([]Tile, len(mm.layers))
|
||||||
|
|
||||||
|
// if !mm.IsInBounds(x, y) {
|
||||||
|
// return tiles
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for i := len(mm.layers) - 1; i >= 0; i-- {
|
||||||
|
// tile := mm.layers[i].TileAt(x, y)
|
||||||
|
|
||||||
|
// if tile != nil && !tile.Transparent() && filter(tile) {
|
||||||
|
// tiles = append(tiles, tile)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return tiles
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (mm *MultilevelMap) TileAt(x int, y int) Tile {
|
||||||
|
// if !mm.IsInBounds(x, y) {
|
||||||
|
// return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for i := len(mm.layers) - 1; i >= 0; i-- {
|
||||||
|
// tile := mm.layers[i].TileAt(x, y)
|
||||||
|
|
||||||
|
// if tile != nil && !tile.Transparent() {
|
||||||
|
// return tile
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (mm *MultilevelMap) IsInBounds(x, y int) bool {
|
||||||
|
// if x < 0 || y < 0 {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if x >= mm.Size().Width() || y >= mm.Size().Height() {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (mm *MultilevelMap) MarkExplored(x, y int) {
|
||||||
|
// for _, m := range mm.layers {
|
||||||
|
// m.MarkExplored(x, y)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (mm *MultilevelMap) ExploredTileAt(x, y int) Tile {
|
||||||
|
// for i := len(mm.layers) - 1; i >= 0; i-- {
|
||||||
|
// tile := mm.layers[i].ExploredTileAt(x, y)
|
||||||
|
|
||||||
|
// if tile != nil && !tile.Transparent() {
|
||||||
|
// return tile
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (mm *MultilevelMap) TileAtHeight(x, y, height int) Tile {
|
||||||
|
// if !mm.IsInBounds(x, y) {
|
||||||
|
// return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if height > len(mm.layers)-1 {
|
||||||
|
// return CreateStaticTile(x, y, TileTypeVoid())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return mm.layers[height].TileAt(x, y)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (mm *MultilevelMap) Tick(dt int64) {
|
||||||
|
// for _, l := range mm.layers {
|
||||||
|
// l.Tick(dt)
|
||||||
|
// }
|
||||||
|
// }
|
368
game/model/world_tile.go
Normal file
368
game/model/world_tile.go
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Material uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaterialGround Material = iota
|
||||||
|
MaterialRock
|
||||||
|
MaterialWall
|
||||||
|
MaterialGrass
|
||||||
|
MaterialVoid
|
||||||
|
MaterialClosedDoor
|
||||||
|
MaterialOpenDoor
|
||||||
|
MaterialStaircaseDown
|
||||||
|
MaterialStaircaseUp
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tile_ItemComponent struct {
|
||||||
|
Item Item_V2
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tile_EntityComponent struct {
|
||||||
|
Entity Entity_V2
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tile_V2 interface {
|
||||||
|
DefaultPresentation() (rune, tcell.Style)
|
||||||
|
Material() Material
|
||||||
|
Passable() bool
|
||||||
|
Opaque() bool
|
||||||
|
Transparent() bool
|
||||||
|
|
||||||
|
Item() *Tile_ItemComponent
|
||||||
|
RemoveItem()
|
||||||
|
WithItem(item Item_V2)
|
||||||
|
|
||||||
|
Entity() *Tile_EntityComponent
|
||||||
|
RemoveEntity()
|
||||||
|
WithEntity(entity Entity_V2)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseTile struct {
|
||||||
|
defaultSymbol rune
|
||||||
|
defaultStyle tcell.Style
|
||||||
|
|
||||||
|
material Material
|
||||||
|
passable, opaque, transparent bool
|
||||||
|
|
||||||
|
item *Tile_ItemComponent
|
||||||
|
entity *Tile_EntityComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTileFromPrototype(prototype Tile_V2, components ...func(*BaseTile)) Tile_V2 {
|
||||||
|
defaultSymbol, defaultStyle := prototype.DefaultPresentation()
|
||||||
|
|
||||||
|
return CreateTile(
|
||||||
|
defaultSymbol,
|
||||||
|
defaultStyle,
|
||||||
|
prototype.Material(),
|
||||||
|
prototype.Passable(),
|
||||||
|
prototype.Opaque(),
|
||||||
|
prototype.Transparent(),
|
||||||
|
components...,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTile(defaultSymbol rune, defaultStyle tcell.Style, material Material, passable, opaque, transparent bool, components ...func(*BaseTile)) Tile_V2 {
|
||||||
|
t := &BaseTile{
|
||||||
|
defaultSymbol: defaultSymbol,
|
||||||
|
defaultStyle: defaultStyle,
|
||||||
|
material: material,
|
||||||
|
passable: passable,
|
||||||
|
opaque: opaque,
|
||||||
|
transparent: transparent,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range components {
|
||||||
|
c(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTile) DefaultPresentation() (rune, tcell.Style) {
|
||||||
|
return t.defaultSymbol, t.defaultStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTile) Material() Material {
|
||||||
|
return t.material
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTile) Passable() bool {
|
||||||
|
return t.passable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTile) Opaque() bool {
|
||||||
|
return t.opaque
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTile) Transparent() bool {
|
||||||
|
return t.transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTile) Item() *Tile_ItemComponent {
|
||||||
|
return t.item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTile) RemoveItem() {
|
||||||
|
t.item = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTile) WithItem(item Item_V2) {
|
||||||
|
t.item = &Tile_ItemComponent{
|
||||||
|
Item: item,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTile) Entity() *Tile_EntityComponent {
|
||||||
|
return t.entity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTile) RemoveEntity() {
|
||||||
|
t.entity = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *BaseTile) WithEntity(entity Entity_V2) {
|
||||||
|
t.entity = &Tile_EntityComponent{
|
||||||
|
Entity: entity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tile_WithEntity(entity Entity_V2) func(*BaseTile) {
|
||||||
|
return func(bt *BaseTile) {
|
||||||
|
bt.entity = &Tile_EntityComponent{
|
||||||
|
Entity: entity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tile_WithItem(item Item_V2) func(*BaseTile) {
|
||||||
|
return func(bt *BaseTile) {
|
||||||
|
bt.item = &Tile_ItemComponent{
|
||||||
|
Item: item,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tile_Void() Tile_V2 {
|
||||||
|
return CreateTile(
|
||||||
|
' ',
|
||||||
|
tcell.StyleDefault,
|
||||||
|
MaterialVoid,
|
||||||
|
false, true, true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tile_Ground() Tile_V2 {
|
||||||
|
return CreateTile(
|
||||||
|
'.',
|
||||||
|
tcell.StyleDefault,
|
||||||
|
MaterialGround,
|
||||||
|
true, false, false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tile_Rock() Tile_V2 {
|
||||||
|
return CreateTile(
|
||||||
|
'█',
|
||||||
|
tcell.StyleDefault,
|
||||||
|
MaterialRock,
|
||||||
|
false, true, false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tile_Wall() Tile_V2 {
|
||||||
|
return CreateTile(
|
||||||
|
'#',
|
||||||
|
tcell.StyleDefault.Background(tcell.ColorGray),
|
||||||
|
MaterialWall,
|
||||||
|
false, true, false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// func TileTypeClosedDoor() TileType {
|
||||||
|
// return TileType{
|
||||||
|
// Material: MaterialClosedDoor,
|
||||||
|
// Passable: false,
|
||||||
|
// Transparent: false,
|
||||||
|
// Presentation: '[',
|
||||||
|
// Opaque: true,
|
||||||
|
// Style: tcell.StyleDefault.Foreground(tcell.ColorLightSteelBlue).Background(tcell.ColorSaddleBrown),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func TileTypeOpenDoor() TileType {
|
||||||
|
// return TileType{
|
||||||
|
// Material: MaterialClosedDoor,
|
||||||
|
// Passable: false,
|
||||||
|
// Transparent: false,
|
||||||
|
// Presentation: '_',
|
||||||
|
// Opaque: false,
|
||||||
|
// Style: tcell.StyleDefault.Foreground(tcell.ColorLightSteelBlue),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
func Tile_StaircaseDown() Tile_V2 {
|
||||||
|
return CreateTile(
|
||||||
|
'≡',
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorDarkSlateGray).Attributes(tcell.AttrBold),
|
||||||
|
MaterialStaircaseDown,
|
||||||
|
true, false, false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tile_StaircaseUp() Tile_V2 {
|
||||||
|
return CreateTile(
|
||||||
|
'^',
|
||||||
|
tcell.StyleDefault.Foreground(tcell.ColorDarkSlateGray).Attributes(tcell.AttrBold),
|
||||||
|
MaterialStaircaseDown,
|
||||||
|
true, false, false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// type Tile interface {
|
||||||
|
// Position() engine.Position
|
||||||
|
// Presentation() (rune, tcell.Style)
|
||||||
|
// Passable() bool
|
||||||
|
// Transparent() bool
|
||||||
|
// Opaque() bool
|
||||||
|
// Type() TileType
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type StaticTile struct {
|
||||||
|
// position engine.Position
|
||||||
|
// t TileType
|
||||||
|
|
||||||
|
// style tcell.Style
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateStaticTile(x, y int, t TileType) Tile {
|
||||||
|
// st := new(StaticTile)
|
||||||
|
|
||||||
|
// st.position = engine.PositionAt(x, y)
|
||||||
|
// st.t = t
|
||||||
|
// st.style = t.Style
|
||||||
|
|
||||||
|
// return st
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateStaticTileWithStyleOverride(x, y int, t TileType, style tcell.Style) Tile {
|
||||||
|
// return &StaticTile{
|
||||||
|
// position: engine.PositionAt(x, y),
|
||||||
|
// t: t,
|
||||||
|
// style: style,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (st *StaticTile) Position() engine.Position {
|
||||||
|
// return st.position
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (st *StaticTile) Presentation() (rune, tcell.Style) {
|
||||||
|
// return st.t.Presentation, st.style
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (st *StaticTile) Passable() bool {
|
||||||
|
// return st.t.Passable
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (st *StaticTile) Transparent() bool {
|
||||||
|
// return st.t.Transparent
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (st *StaticTile) Opaque() bool {
|
||||||
|
// return st.t.Opaque
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (st *StaticTile) Type() TileType {
|
||||||
|
// return st.t
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type ItemTile struct {
|
||||||
|
// position engine.Position
|
||||||
|
// item item.Item
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateItemTile(position engine.Position, item item.Item) *ItemTile {
|
||||||
|
// it := new(ItemTile)
|
||||||
|
|
||||||
|
// it.position = position
|
||||||
|
// it.item = item
|
||||||
|
|
||||||
|
// return it
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *ItemTile) Item() item.Item {
|
||||||
|
// return it.item
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *ItemTile) Position() engine.Position {
|
||||||
|
// return it.position
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *ItemTile) Presentation() (rune, tcell.Style) {
|
||||||
|
// return it.item.Type().TileIcon(), it.item.Type().Style()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *ItemTile) Passable() bool {
|
||||||
|
// return true
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *ItemTile) Transparent() bool {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *ItemTile) Opaque() bool {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (it *ItemTile) Type() TileType {
|
||||||
|
// return TileType{}
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type EntityTile interface {
|
||||||
|
// Entity() npc.MovableEntity
|
||||||
|
// Tile
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type BasicEntityTile struct {
|
||||||
|
// entity npc.MovableEntity
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func CreateBasicEntityTile(entity npc.MovableEntity) *BasicEntityTile {
|
||||||
|
// return &BasicEntityTile{
|
||||||
|
// entity: entity,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (bet *BasicEntityTile) Entity() npc.MovableEntity {
|
||||||
|
// return bet.entity
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (bet *BasicEntityTile) Position() engine.Position {
|
||||||
|
// return bet.entity.Position()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (bet *BasicEntityTile) Presentation() (rune, tcell.Style) {
|
||||||
|
// return bet.entity.Presentation()
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (bet *BasicEntityTile) Passable() bool {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (bet *BasicEntityTile) Transparent() bool {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (bet *BasicEntityTile) Opaque() bool {
|
||||||
|
// return false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (bet *BasicEntityTile) Type() TileType {
|
||||||
|
// return TileType{}
|
||||||
|
// }
|
|
@ -1,67 +0,0 @@
|
||||||
package npc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
"mvvasilev/last_light/game/item"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Direction int
|
|
||||||
|
|
||||||
const (
|
|
||||||
DirectionNone Direction = iota
|
|
||||||
North
|
|
||||||
South
|
|
||||||
West
|
|
||||||
East
|
|
||||||
)
|
|
||||||
|
|
||||||
func DirectionName(dir Direction) string {
|
|
||||||
switch dir {
|
|
||||||
case North:
|
|
||||||
return "North"
|
|
||||||
case South:
|
|
||||||
return "South"
|
|
||||||
case West:
|
|
||||||
return "West"
|
|
||||||
case East:
|
|
||||||
return "East"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func MovementDirectionOffset(dir Direction) (int, int) {
|
|
||||||
switch dir {
|
|
||||||
case North:
|
|
||||||
return 0, -1
|
|
||||||
case South:
|
|
||||||
return 0, 1
|
|
||||||
case West:
|
|
||||||
return -1, 0
|
|
||||||
case East:
|
|
||||||
return 1, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
type Entity interface {
|
|
||||||
UniqueId() uuid.UUID
|
|
||||||
Presentation() (rune, tcell.Style)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MovableEntity interface {
|
|
||||||
Position() engine.Position
|
|
||||||
MoveTo(newPosition engine.Position)
|
|
||||||
|
|
||||||
Entity
|
|
||||||
}
|
|
||||||
|
|
||||||
type EquippedEntity interface {
|
|
||||||
Inventory() *item.EquippedInventory
|
|
||||||
|
|
||||||
Entity
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package npc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NPC interface {
|
|
||||||
Name() string
|
|
||||||
|
|
||||||
MovableEntity
|
|
||||||
}
|
|
||||||
|
|
||||||
type BasicNPC struct {
|
|
||||||
id uuid.UUID
|
|
||||||
name string
|
|
||||||
presentation rune
|
|
||||||
style tcell.Style
|
|
||||||
engine.Positioned
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateNPC(pos engine.Position, name string, presentation rune, style tcell.Style) *BasicNPC {
|
|
||||||
return &BasicNPC{
|
|
||||||
id: uuid.New(),
|
|
||||||
name: name,
|
|
||||||
presentation: presentation,
|
|
||||||
style: style,
|
|
||||||
Positioned: engine.WithPosition(pos),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BasicNPC) Name() string {
|
|
||||||
return c.name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BasicNPC) MoveTo(newPosition engine.Position) {
|
|
||||||
c.Positioned.SetPosition(newPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BasicNPC) UniqueId() uuid.UUID {
|
|
||||||
return c.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *BasicNPC) Presentation() (rune, tcell.Style) {
|
|
||||||
return c.presentation, c.style
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
package npc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
"mvvasilev/last_light/game/item"
|
|
||||||
"mvvasilev/last_light/game/rpg"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RPGNPC interface {
|
|
||||||
NPC
|
|
||||||
rpg.RPGEntity
|
|
||||||
EquippedEntity
|
|
||||||
}
|
|
||||||
|
|
||||||
type BasicRPGNPC struct {
|
|
||||||
inventory *item.EquippedInventory
|
|
||||||
|
|
||||||
*BasicNPC
|
|
||||||
*rpg.BasicRPGEntity
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateRPGNPC(x, y int, name string, representation rune, style tcell.Style, stats map[rpg.Stat]int) *BasicRPGNPC {
|
|
||||||
rpgnpc := &BasicRPGNPC{
|
|
||||||
inventory: item.CreateEquippedInventory(),
|
|
||||||
BasicNPC: CreateNPC(
|
|
||||||
engine.PositionAt(x, y),
|
|
||||||
name,
|
|
||||||
representation,
|
|
||||||
style,
|
|
||||||
),
|
|
||||||
BasicRPGEntity: rpg.CreateBasicRPGEntity(
|
|
||||||
0,
|
|
||||||
stats,
|
|
||||||
map[rpg.Stat][]rpg.StatModifier{},
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
rpgnpc.Heal(rpg.BaseMaxHealth(rpgnpc))
|
|
||||||
|
|
||||||
return rpgnpc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rnpc *BasicRPGNPC) Inventory() *item.EquippedInventory {
|
|
||||||
return rnpc.inventory
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *BasicRPGNPC) CalculateAttack(other rpg.RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType rpg.DamageType) {
|
|
||||||
mainHand := p.inventory.AtSlot(item.EquippedSlotDominantHand)
|
|
||||||
|
|
||||||
switch mh := mainHand.(type) {
|
|
||||||
case rpg.RPGItem:
|
|
||||||
return rpg.PhysicalWeaponAttack(p, mh, other)
|
|
||||||
default:
|
|
||||||
return rpg.UnarmedAttack(p, other)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package player
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
"mvvasilev/last_light/game/item"
|
|
||||||
"mvvasilev/last_light/game/rpg"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Player struct {
|
|
||||||
id uuid.UUID
|
|
||||||
position engine.Position
|
|
||||||
|
|
||||||
inventory *item.EquippedInventory
|
|
||||||
|
|
||||||
*rpg.BasicRPGEntity
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreatePlayer(x, y int, playerStats map[rpg.Stat]int) *Player {
|
|
||||||
p := new(Player)
|
|
||||||
|
|
||||||
p.id = uuid.New()
|
|
||||||
p.position = engine.PositionAt(x, y)
|
|
||||||
p.inventory = item.CreateEquippedInventory()
|
|
||||||
p.BasicRPGEntity = rpg.CreateBasicRPGEntity(
|
|
||||||
0,
|
|
||||||
playerStats,
|
|
||||||
map[rpg.Stat][]rpg.StatModifier{},
|
|
||||||
)
|
|
||||||
|
|
||||||
p.Heal(rpg.BaseMaxHealth(p))
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) UniqueId() uuid.UUID {
|
|
||||||
return p.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) Position() engine.Position {
|
|
||||||
return p.position
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) MoveTo(newPos engine.Position) {
|
|
||||||
p.position = newPos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) Presentation() (rune, tcell.Style) {
|
|
||||||
return '@', tcell.StyleDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) Inventory() *item.EquippedInventory {
|
|
||||||
return p.inventory
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Player) CalculateAttack(other rpg.RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType rpg.DamageType) {
|
|
||||||
mainHand := p.inventory.AtSlot(item.EquippedSlotDominantHand)
|
|
||||||
|
|
||||||
switch mh := mainHand.(type) {
|
|
||||||
case rpg.RPGItem:
|
|
||||||
return rpg.PhysicalWeaponAttack(p, mh, other)
|
|
||||||
default:
|
|
||||||
return rpg.UnarmedAttack(p, other)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
package rpg
|
|
||||||
|
|
||||||
import "slices"
|
|
||||||
|
|
||||||
type RPGEntity interface {
|
|
||||||
BaseStat(stat Stat) int
|
|
||||||
SetBaseStat(stat Stat, value int)
|
|
||||||
|
|
||||||
CollectModifiersForStat(stat Stat) []StatModifier
|
|
||||||
AddStatModifier(modifier StatModifier)
|
|
||||||
RemoveStatModifier(id StatModifierId)
|
|
||||||
|
|
||||||
CurrentHealth() int
|
|
||||||
Heal(health int)
|
|
||||||
Damage(damage int)
|
|
||||||
|
|
||||||
CalculateAttack(other RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType)
|
|
||||||
}
|
|
||||||
|
|
||||||
type BasicRPGEntity struct {
|
|
||||||
stats map[Stat]int
|
|
||||||
|
|
||||||
statModifiers map[Stat][]StatModifier
|
|
||||||
|
|
||||||
currentHealth int
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateBasicRPGEntity(health int, baseStats map[Stat]int, statModifiers map[Stat][]StatModifier) *BasicRPGEntity {
|
|
||||||
return &BasicRPGEntity{
|
|
||||||
stats: baseStats,
|
|
||||||
statModifiers: statModifiers,
|
|
||||||
currentHealth: health,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (brpg *BasicRPGEntity) BaseStat(stat Stat) int {
|
|
||||||
return brpg.stats[stat]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (brpg *BasicRPGEntity) SetBaseStat(stat Stat, value int) {
|
|
||||||
brpg.stats[stat] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (brpg *BasicRPGEntity) CollectModifiersForStat(stat Stat) []StatModifier {
|
|
||||||
modifiers := brpg.statModifiers[stat]
|
|
||||||
|
|
||||||
if modifiers == nil {
|
|
||||||
return []StatModifier{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return modifiers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (brpg *BasicRPGEntity) AddStatModifier(modifier StatModifier) {
|
|
||||||
existing := brpg.statModifiers[modifier.Stat]
|
|
||||||
|
|
||||||
if existing == nil {
|
|
||||||
existing = make([]StatModifier, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
existing = append(existing, modifier)
|
|
||||||
|
|
||||||
brpg.statModifiers[modifier.Stat] = existing
|
|
||||||
}
|
|
||||||
|
|
||||||
func (brpg *BasicRPGEntity) RemoveStatModifier(id StatModifierId) {
|
|
||||||
for k, v := range brpg.statModifiers {
|
|
||||||
for i, sm := range v {
|
|
||||||
if sm.Id == id {
|
|
||||||
brpg.statModifiers[k] = slices.Delete(v, i, i+1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (brpg *BasicRPGEntity) CurrentHealth() int {
|
|
||||||
return brpg.currentHealth
|
|
||||||
}
|
|
||||||
|
|
||||||
func (brpg *BasicRPGEntity) Heal(health int) {
|
|
||||||
maxHealth := BaseMaxHealth(brpg)
|
|
||||||
|
|
||||||
if brpg.currentHealth+health > maxHealth {
|
|
||||||
brpg.currentHealth = maxHealth
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
brpg.currentHealth += health
|
|
||||||
}
|
|
||||||
|
|
||||||
func (brpg *BasicRPGEntity) Damage(damage int) {
|
|
||||||
if brpg.currentHealth-damage < 0 {
|
|
||||||
brpg.currentHealth = 0
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
brpg.currentHealth -= damage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (brpg *BasicRPGEntity) CalculateAttack(other RPGEntity) (hit bool, precisionRoll, evasionRoll int, damage int, damageType DamageType) {
|
|
||||||
return UnarmedAttack(brpg, other)
|
|
||||||
}
|
|
|
@ -1,288 +0,0 @@
|
||||||
package rpg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mvvasilev/last_light/game/item"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RPGItemMetaType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
MetaItemType_Physical_Weapon RPGItemMetaType = iota
|
|
||||||
MetaItemType_Magic_Weapon
|
|
||||||
MetaItemType_Weapon
|
|
||||||
MetaItemType_Physical_Armour
|
|
||||||
MetaItemType_Magic_Armour
|
|
||||||
MetaItemType_Armour
|
|
||||||
)
|
|
||||||
|
|
||||||
type RPGItemType interface {
|
|
||||||
RollDamage() func(victim, attacker RPGEntity) (damage int, dmgType DamageType)
|
|
||||||
MetaTypes() []RPGItemMetaType
|
|
||||||
|
|
||||||
item.ItemType
|
|
||||||
}
|
|
||||||
|
|
||||||
type BasicRPGItemType struct {
|
|
||||||
damageRollFunc func(victim, attacker RPGEntity) (damage int, dmgType DamageType)
|
|
||||||
|
|
||||||
metaTypes []RPGItemMetaType
|
|
||||||
|
|
||||||
*item.BasicItemType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *BasicRPGItemType) RollDamage() func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
return it.damageRollFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *BasicRPGItemType) MetaTypes() []RPGItemMetaType {
|
|
||||||
return it.metaTypes
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeBow() RPGItemType {
|
|
||||||
return &BasicRPGItemType{
|
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
// TODO: Ranged
|
|
||||||
return RollD8(1), DamageType_Physical_Piercing
|
|
||||||
},
|
|
||||||
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
|
||||||
BasicItemType: item.CreateBasicItemType(
|
|
||||||
1000,
|
|
||||||
"Bow",
|
|
||||||
"To shoot arrows with",
|
|
||||||
')',
|
|
||||||
" |)",
|
|
||||||
1,
|
|
||||||
item.EquippedSlotDominantHand,
|
|
||||||
tcell.StyleDefault.Foreground(tcell.ColorBrown),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeLongsword() RPGItemType {
|
|
||||||
return &BasicRPGItemType{
|
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
return RollD8(1), DamageType_Physical_Slashing
|
|
||||||
},
|
|
||||||
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
|
||||||
BasicItemType: item.CreateBasicItemType(
|
|
||||||
1001,
|
|
||||||
"Longsword",
|
|
||||||
"You know nothing.",
|
|
||||||
'/',
|
|
||||||
"╪══",
|
|
||||||
1,
|
|
||||||
item.EquippedSlotDominantHand,
|
|
||||||
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeClub() RPGItemType {
|
|
||||||
return &BasicRPGItemType{
|
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
return RollD8(1), DamageType_Physical_Bludgeoning
|
|
||||||
},
|
|
||||||
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
|
||||||
BasicItemType: item.CreateBasicItemType(
|
|
||||||
1002,
|
|
||||||
"Club",
|
|
||||||
"Bonk",
|
|
||||||
'!',
|
|
||||||
"-══",
|
|
||||||
1,
|
|
||||||
item.EquippedSlotDominantHand,
|
|
||||||
tcell.StyleDefault.Foreground(tcell.ColorSaddleBrown),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeDagger() RPGItemType {
|
|
||||||
return &BasicRPGItemType{
|
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
return RollD6(1), DamageType_Physical_Piercing
|
|
||||||
},
|
|
||||||
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
|
||||||
BasicItemType: item.CreateBasicItemType(
|
|
||||||
1003,
|
|
||||||
"Dagger",
|
|
||||||
"Stabby, stabby",
|
|
||||||
'-',
|
|
||||||
" +─",
|
|
||||||
1,
|
|
||||||
item.EquippedSlotDominantHand,
|
|
||||||
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeHandaxe() RPGItemType {
|
|
||||||
return &BasicRPGItemType{
|
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
return RollD6(1), DamageType_Physical_Slashing
|
|
||||||
},
|
|
||||||
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
|
||||||
BasicItemType: item.CreateBasicItemType(
|
|
||||||
1004,
|
|
||||||
"Handaxe",
|
|
||||||
"Choppy, choppy",
|
|
||||||
'¶',
|
|
||||||
" ─╗",
|
|
||||||
1,
|
|
||||||
item.EquippedSlotDominantHand,
|
|
||||||
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeJavelin() RPGItemType {
|
|
||||||
return &BasicRPGItemType{
|
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
// TODO: Ranged
|
|
||||||
return RollD6(1), DamageType_Physical_Piercing
|
|
||||||
},
|
|
||||||
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
|
||||||
BasicItemType: item.CreateBasicItemType(
|
|
||||||
1005,
|
|
||||||
"Javelin",
|
|
||||||
"Ranged pokey, pokey",
|
|
||||||
'Î',
|
|
||||||
" ─>",
|
|
||||||
20,
|
|
||||||
item.EquippedSlotDominantHand,
|
|
||||||
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeLightHammer() RPGItemType {
|
|
||||||
return &BasicRPGItemType{
|
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
return RollD6(1), DamageType_Physical_Bludgeoning
|
|
||||||
},
|
|
||||||
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
|
||||||
BasicItemType: item.CreateBasicItemType(
|
|
||||||
1006,
|
|
||||||
"Handaxe",
|
|
||||||
"Choppy, choppy",
|
|
||||||
'¶',
|
|
||||||
" ─╗",
|
|
||||||
1,
|
|
||||||
item.EquippedSlotDominantHand,
|
|
||||||
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeMace() RPGItemType {
|
|
||||||
return &BasicRPGItemType{
|
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
return RollD6(1), DamageType_Physical_Bludgeoning
|
|
||||||
},
|
|
||||||
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
|
||||||
BasicItemType: item.CreateBasicItemType(
|
|
||||||
1007,
|
|
||||||
"Mace",
|
|
||||||
"Smashey, smashey",
|
|
||||||
'i',
|
|
||||||
" ─¤",
|
|
||||||
1,
|
|
||||||
item.EquippedSlotDominantHand,
|
|
||||||
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeQuarterstaff() RPGItemType {
|
|
||||||
|
|
||||||
return &BasicRPGItemType{
|
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
return RollD6(1), DamageType_Physical_Bludgeoning
|
|
||||||
},
|
|
||||||
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
|
||||||
BasicItemType: item.CreateBasicItemType(
|
|
||||||
1008,
|
|
||||||
"Quarterstaff",
|
|
||||||
"Whacky, whacky",
|
|
||||||
'|',
|
|
||||||
"───",
|
|
||||||
1,
|
|
||||||
item.EquippedSlotDominantHand,
|
|
||||||
tcell.StyleDefault.Foreground(tcell.ColorSaddleBrown),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeSickle() RPGItemType {
|
|
||||||
return &BasicRPGItemType{
|
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
return RollD6(1), DamageType_Physical_Slashing
|
|
||||||
},
|
|
||||||
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
|
||||||
BasicItemType: item.CreateBasicItemType(
|
|
||||||
1009,
|
|
||||||
"Sickle",
|
|
||||||
"Slicey, slicey?",
|
|
||||||
'?',
|
|
||||||
" ─U",
|
|
||||||
1,
|
|
||||||
item.EquippedSlotDominantHand,
|
|
||||||
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ItemTypeSpear() RPGItemType {
|
|
||||||
return &BasicRPGItemType{
|
|
||||||
damageRollFunc: func(victim, attacker RPGEntity) (damage int, dmgType DamageType) {
|
|
||||||
return RollD8(1), DamageType_Physical_Piercing
|
|
||||||
},
|
|
||||||
metaTypes: []RPGItemMetaType{MetaItemType_Physical_Weapon, MetaItemType_Weapon},
|
|
||||||
BasicItemType: item.CreateBasicItemType(
|
|
||||||
1010,
|
|
||||||
"Spear",
|
|
||||||
"Pokey, pokey",
|
|
||||||
'Î',
|
|
||||||
"──>",
|
|
||||||
1,
|
|
||||||
item.EquippedSlotDominantHand,
|
|
||||||
tcell.StyleDefault.Foreground(tcell.ColorSilver),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type RPGItem interface {
|
|
||||||
Modifiers() []StatModifier
|
|
||||||
RPGType() RPGItemType
|
|
||||||
|
|
||||||
item.Item
|
|
||||||
}
|
|
||||||
|
|
||||||
type BasicRPGItem struct {
|
|
||||||
modifiers []StatModifier
|
|
||||||
rpgType RPGItemType
|
|
||||||
|
|
||||||
item.BasicItem
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *BasicRPGItem) Modifiers() []StatModifier {
|
|
||||||
return i.modifiers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *BasicRPGItem) RPGType() RPGItemType {
|
|
||||||
return i.rpgType
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateRPGItem(name string, style tcell.Style, itemType RPGItemType, modifiers []StatModifier) RPGItem {
|
|
||||||
return &BasicRPGItem{
|
|
||||||
modifiers: modifiers,
|
|
||||||
rpgType: itemType,
|
|
||||||
BasicItem: item.CreateBasicItemWithName(
|
|
||||||
name,
|
|
||||||
style,
|
|
||||||
itemType,
|
|
||||||
1,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,9 +2,8 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/model"
|
||||||
"mvvasilev/last_light/game/rpg"
|
"mvvasilev/last_light/game/systems"
|
||||||
"mvvasilev/last_light/game/turns"
|
|
||||||
"mvvasilev/last_light/game/ui/menu"
|
"mvvasilev/last_light/game/ui/menu"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -16,8 +15,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type CharacterCreationState struct {
|
type CharacterCreationState struct {
|
||||||
turnSystem *turns.TurnSystem
|
turnSystem *systems.TurnSystem
|
||||||
inputSystem *input.InputSystem
|
inputSystem *systems.InputSystem
|
||||||
|
|
||||||
startGame bool
|
startGame bool
|
||||||
|
|
||||||
|
@ -25,26 +24,26 @@ type CharacterCreationState struct {
|
||||||
ccMenu *menu.CharacterCreationMenu
|
ccMenu *menu.CharacterCreationMenu
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateCharacterCreationState(turnSystem *turns.TurnSystem, inputSystem *input.InputSystem) *CharacterCreationState {
|
func CreateCharacterCreationState(turnSystem *systems.TurnSystem, inputSystem *systems.InputSystem) *CharacterCreationState {
|
||||||
|
|
||||||
menuState := &menu.CharacterCreationMenuState{
|
menuState := &menu.CharacterCreationMenuState{
|
||||||
AvailablePoints: 21,
|
AvailablePoints: 21,
|
||||||
CurrentHighlight: 0,
|
CurrentHighlight: 0,
|
||||||
Stats: []*menu.StatState{
|
Stats: []*menu.StatState{
|
||||||
{
|
{
|
||||||
Stat: rpg.Stat_Attributes_Strength,
|
Stat: model.Stat_Attributes_Strength,
|
||||||
Value: 1,
|
Value: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Stat: rpg.Stat_Attributes_Dexterity,
|
Stat: model.Stat_Attributes_Dexterity,
|
||||||
Value: 1,
|
Value: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Stat: rpg.Stat_Attributes_Intelligence,
|
Stat: model.Stat_Attributes_Intelligence,
|
||||||
Value: 1,
|
Value: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Stat: rpg.Stat_Attributes_Constitution,
|
Stat: model.Stat_Attributes_Constitution,
|
||||||
Value: 1,
|
Value: 1,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -58,7 +57,12 @@ func CreateCharacterCreationState(turnSystem *turns.TurnSystem, inputSystem *inp
|
||||||
}
|
}
|
||||||
|
|
||||||
ccs.menuState.RandomizeCharacter = func() {
|
ccs.menuState.RandomizeCharacter = func() {
|
||||||
stats := rpg.RandomStats(21, 1, 20, []rpg.Stat{rpg.Stat_Attributes_Strength, rpg.Stat_Attributes_Constitution, rpg.Stat_Attributes_Intelligence, rpg.Stat_Attributes_Dexterity})
|
stats := model.RandomStats(21, 1, 20, []model.Stat{
|
||||||
|
model.Stat_Attributes_Strength,
|
||||||
|
model.Stat_Attributes_Constitution,
|
||||||
|
model.Stat_Attributes_Intelligence,
|
||||||
|
model.Stat_Attributes_Dexterity,
|
||||||
|
})
|
||||||
|
|
||||||
ccs.menuState.AvailablePoints = 0
|
ccs.menuState.AvailablePoints = 0
|
||||||
ccs.menuState.Stats = []*menu.StatState{}
|
ccs.menuState.Stats = []*menu.StatState{}
|
||||||
|
@ -84,8 +88,8 @@ func CreateCharacterCreationState(turnSystem *turns.TurnSystem, inputSystem *inp
|
||||||
return ccs
|
return ccs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ccs *CharacterCreationState) InputContext() input.Context {
|
func (ccs *CharacterCreationState) InputContext() systems.InputContext {
|
||||||
return input.InputContext_Menu
|
return systems.InputContext_Menu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ccs *CharacterCreationState) IncreaseStatValue() {
|
func (ccs *CharacterCreationState) IncreaseStatValue() {
|
||||||
|
@ -125,7 +129,7 @@ func (ccs *CharacterCreationState) DecreaseStatValue() {
|
||||||
|
|
||||||
func (ccs *CharacterCreationState) OnTick(dt int64) GameState {
|
func (ccs *CharacterCreationState) OnTick(dt int64) GameState {
|
||||||
if ccs.startGame {
|
if ccs.startGame {
|
||||||
stats := map[rpg.Stat]int{}
|
stats := map[model.Stat]int{}
|
||||||
|
|
||||||
for _, s := range ccs.menuState.Stats {
|
for _, s := range ccs.menuState.Stats {
|
||||||
stats[s.Stat] = s.Value
|
stats[s.Stat] = s.Value
|
||||||
|
@ -137,23 +141,23 @@ func (ccs *CharacterCreationState) OnTick(dt int64) GameState {
|
||||||
action := ccs.inputSystem.NextAction()
|
action := ccs.inputSystem.NextAction()
|
||||||
|
|
||||||
switch action {
|
switch action {
|
||||||
case input.InputAction_Menu_HighlightRight:
|
case systems.InputAction_Menu_HighlightRight:
|
||||||
ccs.IncreaseStatValue()
|
ccs.IncreaseStatValue()
|
||||||
case input.InputAction_Menu_HighlightLeft:
|
case systems.InputAction_Menu_HighlightLeft:
|
||||||
ccs.DecreaseStatValue()
|
ccs.DecreaseStatValue()
|
||||||
case input.InputAction_Menu_HighlightDown:
|
case systems.InputAction_Menu_HighlightDown:
|
||||||
if ccs.menuState.CurrentHighlight > len(ccs.menuState.Stats) {
|
if ccs.menuState.CurrentHighlight > len(ccs.menuState.Stats) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
ccs.menuState.CurrentHighlight++
|
ccs.menuState.CurrentHighlight++
|
||||||
case input.InputAction_Menu_HighlightUp:
|
case systems.InputAction_Menu_HighlightUp:
|
||||||
if ccs.menuState.CurrentHighlight == 0 {
|
if ccs.menuState.CurrentHighlight == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
ccs.menuState.CurrentHighlight--
|
ccs.menuState.CurrentHighlight--
|
||||||
case input.InputAction_Menu_Select:
|
case systems.InputAction_Menu_Select:
|
||||||
ccs.ccMenu.SelectHighlight()
|
ccs.ccMenu.SelectHighlight()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,13 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/systems"
|
||||||
"mvvasilev/last_light/game/turns"
|
|
||||||
"mvvasilev/last_light/game/ui"
|
"mvvasilev/last_light/game/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DialogState struct {
|
type DialogState struct {
|
||||||
inputSystem *input.InputSystem
|
inputSystem *systems.InputSystem
|
||||||
turnSystem *turns.TurnSystem
|
turnSystem *systems.TurnSystem
|
||||||
|
|
||||||
prevState GameState
|
prevState GameState
|
||||||
|
|
||||||
|
@ -18,7 +17,7 @@ type DialogState struct {
|
||||||
returnToPreviousState bool
|
returnToPreviousState bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateDialogState(inputSystem *input.InputSystem, turnSystem *turns.TurnSystem, dialog *ui.UIDialog, prevState GameState) *DialogState {
|
func CreateDialogState(inputSystem *systems.InputSystem, turnSystem *systems.TurnSystem, dialog *ui.UIDialog, prevState GameState) *DialogState {
|
||||||
return &DialogState{
|
return &DialogState{
|
||||||
inputSystem: inputSystem,
|
inputSystem: inputSystem,
|
||||||
turnSystem: turnSystem,
|
turnSystem: turnSystem,
|
||||||
|
@ -28,12 +27,12 @@ func CreateDialogState(inputSystem *input.InputSystem, turnSystem *turns.TurnSys
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DialogState) InputContext() input.Context {
|
func (s *DialogState) InputContext() systems.InputContext {
|
||||||
return input.InputContext_Menu
|
return systems.InputContext_Menu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *DialogState) OnTick(dt int64) GameState {
|
func (ds *DialogState) OnTick(dt int64) GameState {
|
||||||
if ds.inputSystem.NextAction() == input.InputAction_Menu_Select {
|
if ds.inputSystem.NextAction() == systems.InputAction_Menu_Select {
|
||||||
ds.returnToPreviousState = true
|
ds.returnToPreviousState = true
|
||||||
ds.dialog.Select()
|
ds.dialog.Select()
|
||||||
}
|
}
|
||||||
|
|
84
game/state/game_over_state.go
Normal file
84
game/state/game_over_state.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/systems"
|
||||||
|
"mvvasilev/last_light/game/ui"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GameOverState struct {
|
||||||
|
inputSystem *systems.InputSystem
|
||||||
|
|
||||||
|
gameOverTitle *engine.Raw
|
||||||
|
deathText *ui.UILabel
|
||||||
|
|
||||||
|
backToMainMenuBtn *ui.UISimpleButton
|
||||||
|
|
||||||
|
returnToMainMenu bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateGameOverState(inputSystem *systems.InputSystem) *GameOverState {
|
||||||
|
gos := &GameOverState{
|
||||||
|
inputSystem: inputSystem,
|
||||||
|
gameOverTitle: engine.CreateRawDrawable(
|
||||||
|
14, 1, tcell.StyleDefault.Attributes(tcell.AttrBold).Foreground(tcell.ColorYellow),
|
||||||
|
"_____ _____ ",
|
||||||
|
"| __ \\ | _ | ",
|
||||||
|
"| | \\/ __ _ _ __ ___ ___ | | | |_ _____ _ __ ",
|
||||||
|
"| | __ / _` | '_ ` _ \\ / _ \\ | | | \\ \\ / / _ \\ '__|",
|
||||||
|
"| |_\\ \\ (_| | | | | | | __/ \\ \\_/ /\\ V / __/ | ",
|
||||||
|
" \\____/\\__,_|_| |_| |_|\\___| \\___/ \\_/ \\___|_| ",
|
||||||
|
),
|
||||||
|
deathText: ui.CreateUILabel(
|
||||||
|
14, 8, 51, 5,
|
||||||
|
fmt.Sprintf(
|
||||||
|
"For all your efforts, your endeavour was ultimately cut short. "+
|
||||||
|
"You have been left bleeding out on the dungeon floor. Your remains "+
|
||||||
|
"will serve as a warning to future seekers of the last light.",
|
||||||
|
),
|
||||||
|
tcell.StyleDefault,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
gos.backToMainMenuBtn = ui.CreateSimpleButton(
|
||||||
|
engine.TERMINAL_SIZE_WIDTH/2-len("Back to Main Menu")/2,
|
||||||
|
16,
|
||||||
|
"Back to Main Menu",
|
||||||
|
tcell.StyleDefault,
|
||||||
|
tcell.StyleDefault.Attributes(tcell.AttrBold),
|
||||||
|
func() {
|
||||||
|
gos.returnToMainMenu = true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
gos.backToMainMenuBtn.Highlight()
|
||||||
|
|
||||||
|
return gos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gos *GameOverState) InputContext() systems.InputContext {
|
||||||
|
return systems.InputContext_Menu
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gos *GameOverState) OnTick(dt int64) GameState {
|
||||||
|
if gos.inputSystem.NextAction() == systems.InputAction_Menu_Select {
|
||||||
|
gos.backToMainMenuBtn.Select()
|
||||||
|
}
|
||||||
|
|
||||||
|
if gos.returnToMainMenu {
|
||||||
|
return CreateMainMenuState(systems.CreateTurnSystem(), gos.inputSystem)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gos *GameOverState) CollectDrawables() []engine.Drawable {
|
||||||
|
return []engine.Drawable{
|
||||||
|
gos.gameOverTitle,
|
||||||
|
gos.deathText,
|
||||||
|
gos.backToMainMenuBtn,
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,11 +2,11 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/systems"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GameState interface {
|
type GameState interface {
|
||||||
InputContext() input.Context
|
InputContext() systems.InputContext
|
||||||
OnTick(dt int64) GameState
|
OnTick(dt int64) GameState
|
||||||
CollectDrawables() []engine.Drawable
|
CollectDrawables() []engine.Drawable
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,18 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/model"
|
||||||
"mvvasilev/last_light/game/player"
|
"mvvasilev/last_light/game/systems"
|
||||||
"mvvasilev/last_light/game/turns"
|
|
||||||
"mvvasilev/last_light/game/ui/menu"
|
"mvvasilev/last_light/game/ui/menu"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InventoryScreenState struct {
|
type InventoryScreenState struct {
|
||||||
inputSystem *input.InputSystem
|
eventLog *engine.GameEventLog
|
||||||
turnSystem *turns.TurnSystem
|
inputSystem *systems.InputSystem
|
||||||
|
turnSystem *systems.TurnSystem
|
||||||
|
dungeon *model.Dungeon
|
||||||
|
|
||||||
prevState GameState
|
prevState GameState
|
||||||
exitMenu bool
|
exitMenu bool
|
||||||
|
@ -20,12 +21,13 @@ type InventoryScreenState struct {
|
||||||
inventoryMenu *menu.PlayerInventoryMenu
|
inventoryMenu *menu.PlayerInventoryMenu
|
||||||
selectedInventorySlot engine.Position
|
selectedInventorySlot engine.Position
|
||||||
|
|
||||||
player *player.Player
|
player *model.Player_V2
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateInventoryScreenState(inputSystem *input.InputSystem, turnSystem *turns.TurnSystem, player *player.Player, prevState GameState) *InventoryScreenState {
|
func CreateInventoryScreenState(eventLog *engine.GameEventLog, dungeon *model.Dungeon, inputSystem *systems.InputSystem, turnSystem *systems.TurnSystem, player *model.Player_V2, prevState GameState) *InventoryScreenState {
|
||||||
iss := new(InventoryScreenState)
|
iss := new(InventoryScreenState)
|
||||||
|
|
||||||
|
iss.eventLog = eventLog
|
||||||
iss.inputSystem = inputSystem
|
iss.inputSystem = inputSystem
|
||||||
iss.turnSystem = turnSystem
|
iss.turnSystem = turnSystem
|
||||||
iss.prevState = prevState
|
iss.prevState = prevState
|
||||||
|
@ -33,12 +35,13 @@ func CreateInventoryScreenState(inputSystem *input.InputSystem, turnSystem *turn
|
||||||
iss.selectedInventorySlot = engine.PositionAt(0, 0)
|
iss.selectedInventorySlot = engine.PositionAt(0, 0)
|
||||||
iss.exitMenu = false
|
iss.exitMenu = false
|
||||||
iss.inventoryMenu = menu.CreatePlayerInventoryMenu(43, 0, player.Inventory(), tcell.StyleDefault, tcell.StyleDefault.Background(tcell.ColorDarkSlateGray))
|
iss.inventoryMenu = menu.CreatePlayerInventoryMenu(43, 0, player.Inventory(), tcell.StyleDefault, tcell.StyleDefault.Background(tcell.ColorDarkSlateGray))
|
||||||
|
iss.dungeon = dungeon
|
||||||
|
|
||||||
return iss
|
return iss
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InventoryScreenState) InputContext() input.Context {
|
func (s *InventoryScreenState) InputContext() systems.InputContext {
|
||||||
return input.InputContext_Inventory
|
return systems.InputContext_Inventory
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iss *InventoryScreenState) OnTick(dt int64) (nextState GameState) {
|
func (iss *InventoryScreenState) OnTick(dt int64) (nextState GameState) {
|
||||||
|
@ -46,32 +49,53 @@ func (iss *InventoryScreenState) OnTick(dt int64) (nextState GameState) {
|
||||||
nextState = iss
|
nextState = iss
|
||||||
|
|
||||||
switch nextAction {
|
switch nextAction {
|
||||||
case input.InputAction_Menu_Exit:
|
case systems.InputAction_Menu_Exit:
|
||||||
nextState = iss.prevState
|
nextState = iss.prevState
|
||||||
case input.InputAction_DropItem:
|
case systems.InputAction_InteractItem:
|
||||||
|
item := iss.player.Inventory().ItemAt(iss.selectedInventorySlot.XY())
|
||||||
|
|
||||||
|
if item == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.Usable() != nil {
|
||||||
|
item.Usable().Use(iss.eventLog, iss.dungeon, iss.player)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.Equippable() != nil {
|
||||||
|
if iss.player.Inventory().AtSlot(item.Equippable().Slot) != nil {
|
||||||
|
iss.player.Inventory().Push(iss.player.Inventory().AtSlot(item.Equippable().Slot))
|
||||||
|
}
|
||||||
|
|
||||||
|
iss.player.Inventory().Equip(item, item.Equippable().Slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
iss.player.Inventory().ReduceQuantityAt(iss.selectedInventorySlot.X(), iss.selectedInventorySlot.Y(), 1)
|
||||||
|
|
||||||
|
case systems.InputAction_DropItem:
|
||||||
iss.player.Inventory().Drop(iss.selectedInventorySlot.XY())
|
iss.player.Inventory().Drop(iss.selectedInventorySlot.XY())
|
||||||
case input.InputAction_Menu_HighlightUp:
|
case systems.InputAction_Menu_HighlightUp:
|
||||||
if iss.selectedInventorySlot.Y() == 0 {
|
if iss.selectedInventorySlot.Y() == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, -1)
|
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, -1)
|
||||||
iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY())
|
iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY())
|
||||||
case input.InputAction_Menu_HighlightDown:
|
case systems.InputAction_Menu_HighlightDown:
|
||||||
if iss.selectedInventorySlot.Y() == iss.player.Inventory().Shape().Height()-1 {
|
if iss.selectedInventorySlot.Y() == iss.player.Inventory().Shape().Height()-1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, +1)
|
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(0, +1)
|
||||||
iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY())
|
iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY())
|
||||||
case input.InputAction_Menu_HighlightLeft:
|
case systems.InputAction_Menu_HighlightLeft:
|
||||||
if iss.selectedInventorySlot.X() == 0 {
|
if iss.selectedInventorySlot.X() == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(-1, 0)
|
iss.selectedInventorySlot = iss.selectedInventorySlot.WithOffset(-1, 0)
|
||||||
iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY())
|
iss.inventoryMenu.SelectSlot(iss.selectedInventorySlot.XY())
|
||||||
case input.InputAction_Menu_HighlightRight:
|
case systems.InputAction_Menu_HighlightRight:
|
||||||
if iss.selectedInventorySlot.X() == iss.player.Inventory().Shape().Width()-1 {
|
if iss.selectedInventorySlot.X() == iss.player.Inventory().Shape().Width()-1 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,15 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/systems"
|
||||||
"mvvasilev/last_light/game/turns"
|
|
||||||
"mvvasilev/last_light/game/ui"
|
"mvvasilev/last_light/game/ui"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MainMenuState struct {
|
type MainMenuState struct {
|
||||||
turnSystem *turns.TurnSystem
|
turnSystem *systems.TurnSystem
|
||||||
inputSystem *input.InputSystem
|
inputSystem *systems.InputSystem
|
||||||
|
|
||||||
menuTitle *engine.Raw
|
menuTitle *engine.Raw
|
||||||
buttons []*ui.UISimpleButton
|
buttons []*ui.UISimpleButton
|
||||||
|
@ -21,7 +20,7 @@ type MainMenuState struct {
|
||||||
startNewGame bool
|
startNewGame bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateMainMenuState(turnSystem *turns.TurnSystem, inputSystem *input.InputSystem) *MainMenuState {
|
func CreateMainMenuState(turnSystem *systems.TurnSystem, inputSystem *systems.InputSystem) *MainMenuState {
|
||||||
turnSystem.Clear()
|
turnSystem.Clear()
|
||||||
|
|
||||||
state := new(MainMenuState)
|
state := new(MainMenuState)
|
||||||
|
@ -43,7 +42,7 @@ func CreateMainMenuState(turnSystem *turns.TurnSystem, inputSystem *input.InputS
|
||||||
state.buttons = append(state.buttons, ui.CreateSimpleButton(11, 7, "New Game", tcell.StyleDefault, highlightStyle, func() {
|
state.buttons = append(state.buttons, ui.CreateSimpleButton(11, 7, "New Game", tcell.StyleDefault, highlightStyle, func() {
|
||||||
state.startNewGame = true
|
state.startNewGame = true
|
||||||
}))
|
}))
|
||||||
state.buttons = append(state.buttons, ui.CreateSimpleButton(11, 9, "Load Game", tcell.StyleDefault, highlightStyle, func() {
|
state.buttons = append(state.buttons, ui.CreateSimpleButton(11, 9, "Key Bindings // TODO", tcell.StyleDefault, highlightStyle, func() {
|
||||||
|
|
||||||
}))
|
}))
|
||||||
state.buttons = append(state.buttons, ui.CreateSimpleButton(11, 11, "Quit", tcell.StyleDefault, highlightStyle, func() {
|
state.buttons = append(state.buttons, ui.CreateSimpleButton(11, 11, "Quit", tcell.StyleDefault, highlightStyle, func() {
|
||||||
|
@ -56,26 +55,26 @@ func CreateMainMenuState(turnSystem *turns.TurnSystem, inputSystem *input.InputS
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *MainMenuState) InputContext() input.Context {
|
func (s *MainMenuState) InputContext() systems.InputContext {
|
||||||
return input.InputContext_Menu
|
return systems.InputContext_Menu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mms *MainMenuState) OnTick(dt int64) GameState {
|
func (mms *MainMenuState) OnTick(dt int64) GameState {
|
||||||
nextAction := mms.inputSystem.NextAction()
|
nextAction := mms.inputSystem.NextAction()
|
||||||
|
|
||||||
if nextAction == input.InputAction_Menu_HighlightDown {
|
if nextAction == systems.InputAction_Menu_HighlightDown {
|
||||||
mms.buttons[mms.currButtonSelected].Unhighlight()
|
mms.buttons[mms.currButtonSelected].Unhighlight()
|
||||||
mms.currButtonSelected = engine.LimitIncrement(mms.currButtonSelected, 2)
|
mms.currButtonSelected = engine.LimitIncrement(mms.currButtonSelected, 2)
|
||||||
mms.buttons[mms.currButtonSelected].Highlight()
|
mms.buttons[mms.currButtonSelected].Highlight()
|
||||||
}
|
}
|
||||||
|
|
||||||
if nextAction == input.InputAction_Menu_HighlightUp {
|
if nextAction == systems.InputAction_Menu_HighlightUp {
|
||||||
mms.buttons[mms.currButtonSelected].Unhighlight()
|
mms.buttons[mms.currButtonSelected].Unhighlight()
|
||||||
mms.currButtonSelected = engine.LimitDecrement(mms.currButtonSelected, 0)
|
mms.currButtonSelected = engine.LimitDecrement(mms.currButtonSelected, 0)
|
||||||
mms.buttons[mms.currButtonSelected].Highlight()
|
mms.buttons[mms.currButtonSelected].Highlight()
|
||||||
}
|
}
|
||||||
|
|
||||||
if nextAction == input.InputAction_Menu_Select {
|
if nextAction == systems.InputAction_Menu_Select {
|
||||||
mms.buttons[mms.currButtonSelected].Select()
|
mms.buttons[mms.currButtonSelected].Select()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,15 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/systems"
|
||||||
"mvvasilev/last_light/game/turns"
|
|
||||||
"mvvasilev/last_light/game/ui"
|
"mvvasilev/last_light/game/ui"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PauseGameState struct {
|
type PauseGameState struct {
|
||||||
turnSystem *turns.TurnSystem
|
turnSystem *systems.TurnSystem
|
||||||
inputSystem *input.InputSystem
|
inputSystem *systems.InputSystem
|
||||||
|
|
||||||
prevState GameState
|
prevState GameState
|
||||||
|
|
||||||
|
@ -23,7 +22,7 @@ type PauseGameState struct {
|
||||||
currButtonSelected int
|
currButtonSelected int
|
||||||
}
|
}
|
||||||
|
|
||||||
func PauseGame(prevState GameState, turnSystem *turns.TurnSystem, inputSystem *input.InputSystem) *PauseGameState {
|
func PauseGame(prevState GameState, turnSystem *systems.TurnSystem, inputSystem *systems.InputSystem) *PauseGameState {
|
||||||
s := new(PauseGameState)
|
s := new(PauseGameState)
|
||||||
|
|
||||||
s.turnSystem = turnSystem
|
s.turnSystem = turnSystem
|
||||||
|
@ -67,23 +66,23 @@ func PauseGame(prevState GameState, turnSystem *turns.TurnSystem, inputSystem *i
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PauseGameState) InputContext() input.Context {
|
func (s *PauseGameState) InputContext() systems.InputContext {
|
||||||
return input.InputContext_Menu
|
return systems.InputContext_Menu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pg *PauseGameState) OnTick(dt int64) GameState {
|
func (pg *PauseGameState) OnTick(dt int64) GameState {
|
||||||
switch pg.inputSystem.NextAction() {
|
switch pg.inputSystem.NextAction() {
|
||||||
case input.InputAction_Menu_Exit:
|
case systems.InputAction_Menu_Exit:
|
||||||
pg.unpauseGame = true
|
pg.unpauseGame = true
|
||||||
case input.InputAction_Menu_HighlightDown:
|
case systems.InputAction_Menu_HighlightDown:
|
||||||
pg.buttons[pg.currButtonSelected].Unhighlight()
|
pg.buttons[pg.currButtonSelected].Unhighlight()
|
||||||
pg.currButtonSelected = engine.LimitIncrement(pg.currButtonSelected, 1)
|
pg.currButtonSelected = engine.LimitIncrement(pg.currButtonSelected, 1)
|
||||||
pg.buttons[pg.currButtonSelected].Highlight()
|
pg.buttons[pg.currButtonSelected].Highlight()
|
||||||
case input.InputAction_Menu_HighlightUp:
|
case systems.InputAction_Menu_HighlightUp:
|
||||||
pg.buttons[pg.currButtonSelected].Unhighlight()
|
pg.buttons[pg.currButtonSelected].Unhighlight()
|
||||||
pg.currButtonSelected = engine.LimitDecrement(pg.currButtonSelected, 0)
|
pg.currButtonSelected = engine.LimitDecrement(pg.currButtonSelected, 0)
|
||||||
pg.buttons[pg.currButtonSelected].Highlight()
|
pg.buttons[pg.currButtonSelected].Highlight()
|
||||||
case input.InputAction_Menu_Select:
|
case systems.InputAction_Menu_Select:
|
||||||
pg.buttons[pg.currButtonSelected].Select()
|
pg.buttons[pg.currButtonSelected].Select()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,31 +3,27 @@ package state
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/model"
|
||||||
"mvvasilev/last_light/game/npc"
|
"mvvasilev/last_light/game/systems"
|
||||||
"mvvasilev/last_light/game/player"
|
|
||||||
"mvvasilev/last_light/game/rpg"
|
|
||||||
"mvvasilev/last_light/game/turns"
|
|
||||||
"mvvasilev/last_light/game/ui"
|
"mvvasilev/last_light/game/ui"
|
||||||
"mvvasilev/last_light/game/world"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/gdamore/tcell/v2/views"
|
"github.com/gdamore/tcell/v2/views"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PlayingState struct {
|
type PlayingState struct {
|
||||||
turnSystem *turns.TurnSystem
|
turnSystem *systems.TurnSystem
|
||||||
inputSystem *input.InputSystem
|
inputSystem *systems.InputSystem
|
||||||
|
|
||||||
player *player.Player
|
player *model.Player_V2
|
||||||
someNPC npc.RPGNPC
|
someNPC model.Entity_V2
|
||||||
|
|
||||||
eventLog *engine.GameEventLog
|
eventLog *engine.GameEventLog
|
||||||
uiEventLog *ui.UIEventLog
|
uiEventLog *ui.UIEventLog
|
||||||
|
|
||||||
healthBar *ui.UIHealthBar
|
healthBar *ui.UIHealthBar
|
||||||
|
|
||||||
dungeon *world.Dungeon
|
dungeon *model.Dungeon
|
||||||
|
|
||||||
viewport *engine.Viewport
|
viewport *engine.Viewport
|
||||||
|
|
||||||
|
@ -36,7 +32,7 @@ type PlayingState struct {
|
||||||
nextGameState GameState
|
nextGameState GameState
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSystem, playerStats map[rpg.Stat]int) *PlayingState {
|
func CreatePlayingState(turnSystem *systems.TurnSystem, inputSystem *systems.InputSystem, playerStats map[model.Stat]int) *PlayingState {
|
||||||
turnSystem.Clear()
|
turnSystem.Clear()
|
||||||
|
|
||||||
s := new(PlayingState)
|
s := new(PlayingState)
|
||||||
|
@ -46,11 +42,11 @@ func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSy
|
||||||
|
|
||||||
mapSize := engine.SizeOf(128, 128)
|
mapSize := engine.SizeOf(128, 128)
|
||||||
|
|
||||||
s.dungeon = world.CreateDungeon(mapSize.Width(), mapSize.Height(), 1)
|
s.dungeon = model.CreateDungeon(mapSize.Width(), mapSize.Height(), 1)
|
||||||
|
|
||||||
s.player = player.CreatePlayer(
|
s.player = model.CreatePlayer_V2(
|
||||||
s.dungeon.CurrentLevel().PlayerSpawnPoint().X(),
|
s.dungeon.CurrentLevel().Ground().PlayerSpawnPoint().Position.X(),
|
||||||
s.dungeon.CurrentLevel().PlayerSpawnPoint().Y(),
|
s.dungeon.CurrentLevel().Ground().PlayerSpawnPoint().Position.Y(),
|
||||||
playerStats,
|
playerStats,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -58,30 +54,34 @@ func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSy
|
||||||
requeue = true
|
requeue = true
|
||||||
complete = false
|
complete = false
|
||||||
|
|
||||||
|
if s.player.HealthData().IsDead {
|
||||||
|
s.nextGameState = CreateGameOverState(inputSystem)
|
||||||
|
}
|
||||||
|
|
||||||
switch inputSystem.NextAction() {
|
switch inputSystem.NextAction() {
|
||||||
case input.InputAction_PauseGame:
|
case systems.InputAction_PauseGame:
|
||||||
s.nextGameState = PauseGame(s, s.turnSystem, s.inputSystem)
|
s.nextGameState = PauseGame(s, s.turnSystem, s.inputSystem)
|
||||||
case input.InputAction_OpenInventory:
|
case systems.InputAction_OpenInventory:
|
||||||
s.nextGameState = CreateInventoryScreenState(s.inputSystem, s.turnSystem, s.player, s)
|
s.nextGameState = CreateInventoryScreenState(s.eventLog, s.dungeon, s.inputSystem, s.turnSystem, s.player, s)
|
||||||
case input.InputAction_PickUpItem:
|
case systems.InputAction_PickUpItem:
|
||||||
s.PickUpItemUnderPlayer()
|
s.PickUpItemUnderPlayer()
|
||||||
complete = true
|
complete = true
|
||||||
case input.InputAction_Interact:
|
case systems.InputAction_Interact:
|
||||||
s.InteractBelowPlayer()
|
s.InteractBelowPlayer()
|
||||||
complete = true
|
complete = true
|
||||||
case input.InputAction_OpenLogs:
|
case systems.InputAction_OpenLogs:
|
||||||
s.viewShortLogs = !s.viewShortLogs
|
s.viewShortLogs = !s.viewShortLogs
|
||||||
case input.InputAction_MovePlayer_East:
|
case systems.InputAction_MovePlayer_East:
|
||||||
s.MovePlayer(npc.East)
|
s.MovePlayer(model.East)
|
||||||
complete = true
|
complete = true
|
||||||
case input.InputAction_MovePlayer_West:
|
case systems.InputAction_MovePlayer_West:
|
||||||
s.MovePlayer(npc.West)
|
s.MovePlayer(model.West)
|
||||||
complete = true
|
complete = true
|
||||||
case input.InputAction_MovePlayer_North:
|
case systems.InputAction_MovePlayer_North:
|
||||||
s.MovePlayer(npc.North)
|
s.MovePlayer(model.North)
|
||||||
complete = true
|
complete = true
|
||||||
case input.InputAction_MovePlayer_South:
|
case systems.InputAction_MovePlayer_South:
|
||||||
s.MovePlayer(npc.South)
|
s.MovePlayer(model.South)
|
||||||
complete = true
|
complete = true
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
@ -89,13 +89,12 @@ func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSy
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
||||||
s.someNPC = npc.CreateRPGNPC(
|
s.someNPC = model.CreateEntity(
|
||||||
s.dungeon.CurrentLevel().NextLevelStaircase().X(),
|
model.WithPosition(s.dungeon.CurrentLevel().Ground().NextLevelStaircase().Position),
|
||||||
s.dungeon.CurrentLevel().NextLevelStaircase().Y(),
|
model.WithName("NPC"),
|
||||||
"NPC",
|
model.WithPresentation('n', tcell.StyleDefault),
|
||||||
'n',
|
model.WithStats(model.RandomStats(21, 1, 20, []model.Stat{model.Stat_Attributes_Strength, model.Stat_Attributes_Constitution, model.Stat_Attributes_Intelligence, model.Stat_Attributes_Dexterity})),
|
||||||
tcell.StyleDefault,
|
model.WithHealthData(20, 20, false),
|
||||||
rpg.RandomStats(21, 1, 20, []rpg.Stat{rpg.Stat_Attributes_Strength, rpg.Stat_Attributes_Constitution, rpg.Stat_Attributes_Intelligence, rpg.Stat_Attributes_Dexterity}),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
s.turnSystem.Schedule(20, func() (complete bool, requeue bool) {
|
s.turnSystem.Schedule(20, func() (complete bool, requeue bool) {
|
||||||
|
@ -107,14 +106,14 @@ func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSy
|
||||||
s.eventLog = engine.CreateGameEventLog(100)
|
s.eventLog = engine.CreateGameEventLog(100)
|
||||||
|
|
||||||
s.uiEventLog = ui.CreateUIEventLog(0, 17, 80, 7, s.eventLog, tcell.StyleDefault)
|
s.uiEventLog = ui.CreateUIEventLog(0, 17, 80, 7, s.eventLog, tcell.StyleDefault)
|
||||||
s.healthBar = ui.CreateHealthBar(68, 0, 12, 3, s.player.CurrentHealth(), rpg.BaseMaxHealth(s.player), tcell.StyleDefault)
|
s.healthBar = ui.CreateHealthBar(68, 0, 12, 3, s.player, tcell.StyleDefault)
|
||||||
|
|
||||||
s.dungeon.CurrentLevel().AddEntity(s.player)
|
s.dungeon.CurrentLevel().AddEntity(s.player)
|
||||||
s.dungeon.CurrentLevel().AddEntity(s.someNPC)
|
s.dungeon.CurrentLevel().AddEntity(s.someNPC)
|
||||||
|
|
||||||
s.viewport = engine.CreateViewport(
|
s.viewport = engine.CreateViewport(
|
||||||
engine.PositionAt(0, 0),
|
engine.PositionAt(0, 0),
|
||||||
s.dungeon.CurrentLevel().PlayerSpawnPoint(),
|
s.dungeon.CurrentLevel().Ground().PlayerSpawnPoint().Position,
|
||||||
engine.SizeOf(80, 24),
|
engine.SizeOf(80, 24),
|
||||||
tcell.StyleDefault,
|
tcell.StyleDefault,
|
||||||
)
|
)
|
||||||
|
@ -124,53 +123,89 @@ func CreatePlayingState(turnSystem *turns.TurnSystem, inputSystem *input.InputSy
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PlayingState) InputContext() input.Context {
|
func (s *PlayingState) InputContext() systems.InputContext {
|
||||||
return input.InputContext_Play
|
return systems.InputContext_Play
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) MovePlayer(direction npc.Direction) {
|
func (ps *PlayingState) MovePlayer(direction model.Direction) {
|
||||||
if direction == npc.DirectionNone {
|
if direction == model.DirectionNone {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newPlayerPos := ps.player.Position().WithOffset(npc.MovementDirectionOffset(direction))
|
newPlayerPos := ps.player.Position().WithOffset(model.MovementDirectionOffset(direction))
|
||||||
|
|
||||||
if ps.dungeon.CurrentLevel().IsTilePassable(newPlayerPos.XY()) {
|
|
||||||
dx, dy := npc.MovementDirectionOffset(direction)
|
|
||||||
ps.dungeon.CurrentLevel().MoveEntity(ps.player.UniqueId(), dx, dy)
|
|
||||||
ps.viewport.SetCenter(ps.player.Position())
|
|
||||||
|
|
||||||
ps.eventLog.Log("You moved " + npc.DirectionName(direction))
|
|
||||||
}
|
|
||||||
|
|
||||||
ent := ps.dungeon.CurrentLevel().EntityAt(newPlayerPos.XY())
|
ent := ps.dungeon.CurrentLevel().EntityAt(newPlayerPos.XY())
|
||||||
|
|
||||||
// We are moving into an entity. Attack it.
|
// We are moving into an entity with health data. Attack it.
|
||||||
if ent != nil {
|
if ent != nil && ent.HealthData() != nil {
|
||||||
switch rpge := ent.(type) {
|
if ent.HealthData().IsDead {
|
||||||
case npc.RPGNPC:
|
// TODO: If the entity is dead, the player should be able to move through it.
|
||||||
hit, precision, evasion, dmg, dmgType := ps.player.CalculateAttack(rpge)
|
|
||||||
|
|
||||||
if !hit {
|
|
||||||
ps.eventLog.Log(fmt.Sprintf("You attacked %v, but missed ( %v Evasion vs %v Precision)", rpge.Name(), evasion, precision))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rpge.Damage(dmg)
|
ExecuteAttack(ps.eventLog, ps.player, ent)
|
||||||
ps.eventLog.Log(fmt.Sprintf("You attacked %v, and hit for %v %v damage", rpge.Name(), dmg, rpg.DamageTypeName(dmgType)))
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ps.dungeon.CurrentLevel().IsTilePassable(newPlayerPos.XY()) {
|
||||||
|
ps.dungeon.CurrentLevel().MoveEntityTo(ps.player.UniqueId(), newPlayerPos.X(), newPlayerPos.Y())
|
||||||
|
ps.viewport.SetCenter(ps.player.Position())
|
||||||
|
|
||||||
|
ps.eventLog.Log("You moved " + model.DirectionName(direction))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExecuteAttack(eventLog *engine.GameEventLog, attacker, victim model.Entity_V2) {
|
||||||
|
hit, precision, evasion, dmg, dmgType := CalculateAttack(attacker, victim)
|
||||||
|
|
||||||
|
attackerName := "Unknown"
|
||||||
|
|
||||||
|
if attacker.Named() != nil {
|
||||||
|
attackerName = attacker.Named().Name
|
||||||
|
}
|
||||||
|
|
||||||
|
victimName := "Unknown"
|
||||||
|
|
||||||
|
if victim.Named() != nil {
|
||||||
|
victimName = victim.Named().Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hit {
|
||||||
|
eventLog.Log(fmt.Sprintf("%s attacked %s, but missed ( %v Evasion vs %v Precision)", attackerName, victimName, evasion, precision))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
victim.HealthData().Health -= dmg
|
||||||
|
|
||||||
|
if victim.HealthData().Health <= 0 {
|
||||||
|
victim.HealthData().IsDead = true
|
||||||
|
eventLog.Log(fmt.Sprintf("%s attacked %s, and was victorious ( %v Evasion vs %v Precision)", attackerName, victimName, evasion, precision))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
eventLog.Log(fmt.Sprintf("%s attacked %s, and hit for %v %v damage", attackerName, victimName, dmg, model.DamageTypeName(dmgType)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalculateAttack(attacker, victim model.Entity_V2) (hit bool, precisionRoll, evasionRoll int, damage int, damageType model.DamageType) {
|
||||||
|
if attacker.Equipped() != nil && attacker.Equipped().Inventory.AtSlot(model.EquippedSlotDominantHand) != nil {
|
||||||
|
weapon := attacker.Equipped().Inventory.AtSlot(model.EquippedSlotDominantHand)
|
||||||
|
|
||||||
|
return model.PhysicalWeaponAttack(attacker, weapon, victim)
|
||||||
|
} else {
|
||||||
|
return model.UnarmedAttack(attacker, victim)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) InteractBelowPlayer() {
|
func (ps *PlayingState) InteractBelowPlayer() {
|
||||||
playerPos := ps.player.Position()
|
playerPos := ps.player.Position()
|
||||||
|
|
||||||
if playerPos == ps.dungeon.CurrentLevel().NextLevelStaircase() {
|
if playerPos == ps.dungeon.CurrentLevel().Ground().NextLevelStaircase().Position {
|
||||||
ps.SwitchToNextLevel()
|
ps.SwitchToNextLevel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if playerPos == ps.dungeon.CurrentLevel().PreviousLevelStaircase() {
|
if playerPos == ps.dungeon.CurrentLevel().Ground().PreviousLevelStaircase().Position {
|
||||||
ps.SwitchToPreviousLevel()
|
ps.SwitchToPreviousLevel()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -200,11 +235,11 @@ func (ps *PlayingState) SwitchToNextLevel() {
|
||||||
|
|
||||||
ps.dungeon.MoveToNextLevel()
|
ps.dungeon.MoveToNextLevel()
|
||||||
|
|
||||||
ps.player.MoveTo(ps.dungeon.CurrentLevel().PlayerSpawnPoint())
|
ps.player.Positioned().Position = ps.dungeon.CurrentLevel().Ground().PlayerSpawnPoint().Position
|
||||||
|
|
||||||
ps.viewport = engine.CreateViewport(
|
ps.viewport = engine.CreateViewport(
|
||||||
engine.PositionAt(0, 0),
|
engine.PositionAt(0, 0),
|
||||||
ps.dungeon.CurrentLevel().PlayerSpawnPoint(),
|
ps.dungeon.CurrentLevel().Ground().PlayerSpawnPoint().Position,
|
||||||
engine.SizeOf(80, 24),
|
engine.SizeOf(80, 24),
|
||||||
tcell.StyleDefault,
|
tcell.StyleDefault,
|
||||||
)
|
)
|
||||||
|
@ -236,11 +271,11 @@ func (ps *PlayingState) SwitchToPreviousLevel() {
|
||||||
|
|
||||||
ps.dungeon.MoveToPreviousLevel()
|
ps.dungeon.MoveToPreviousLevel()
|
||||||
|
|
||||||
ps.player.MoveTo(ps.dungeon.CurrentLevel().NextLevelStaircase())
|
ps.player.Positioned().Position = ps.dungeon.CurrentLevel().Ground().NextLevelStaircase().Position
|
||||||
|
|
||||||
ps.viewport = engine.CreateViewport(
|
ps.viewport = engine.CreateViewport(
|
||||||
engine.PositionAt(0, 0),
|
engine.PositionAt(0, 0),
|
||||||
ps.dungeon.CurrentLevel().NextLevelStaircase(),
|
ps.dungeon.CurrentLevel().Ground().NextLevelStaircase().Position,
|
||||||
engine.SizeOf(80, 24),
|
engine.SizeOf(80, 24),
|
||||||
tcell.StyleDefault,
|
tcell.StyleDefault,
|
||||||
)
|
)
|
||||||
|
@ -263,9 +298,12 @@ func (ps *PlayingState) PickUpItemUnderPlayer() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
itemName, _ := item.Name()
|
if item.Named() != nil {
|
||||||
|
itemName := item.Named().Name
|
||||||
ps.eventLog.Log("You picked up " + itemName)
|
ps.eventLog.Log("You picked up " + itemName)
|
||||||
|
} else {
|
||||||
|
ps.eventLog.Log("You picked up an item")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) HasLineOfSight(start, end engine.Position) bool {
|
func (ps *PlayingState) HasLineOfSight(start, end engine.Position) bool {
|
||||||
|
@ -285,25 +323,30 @@ func (ps *PlayingState) PlayerWithinHitRange(pos engine.Position) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *PlayingState) CalcPathToPlayerAndMove() {
|
func (ps *PlayingState) CalcPathToPlayerAndMove() {
|
||||||
|
if ps.someNPC.HealthData().IsDead {
|
||||||
|
ps.dungeon.CurrentLevel().DropEntity(ps.someNPC.UniqueId())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
playerVisibleAndInRange := false
|
playerVisibleAndInRange := false
|
||||||
|
|
||||||
if ps.someNPC.Position().Distance(ps.player.Position()) < 20 && ps.HasLineOfSight(ps.someNPC.Position(), ps.player.Position()) {
|
if ps.someNPC.Positioned().Position.Distance(ps.player.Position()) < 20 && ps.HasLineOfSight(ps.someNPC.Positioned().Position, ps.player.Position()) {
|
||||||
playerVisibleAndInRange = true
|
playerVisibleAndInRange = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !playerVisibleAndInRange {
|
if !playerVisibleAndInRange {
|
||||||
randomMove := npc.Direction(engine.RandInt(int(npc.DirectionNone), int(npc.East)))
|
randomMove := model.Direction(engine.RandInt(int(model.DirectionNone), int(model.East)))
|
||||||
|
|
||||||
nextPos := ps.someNPC.Position()
|
nextPos := ps.someNPC.Positioned().Position
|
||||||
|
|
||||||
switch randomMove {
|
switch randomMove {
|
||||||
case npc.North:
|
case model.North:
|
||||||
nextPos = nextPos.WithOffset(0, -1)
|
nextPos = nextPos.WithOffset(0, -1)
|
||||||
case npc.South:
|
case model.South:
|
||||||
nextPos = nextPos.WithOffset(0, +1)
|
nextPos = nextPos.WithOffset(0, +1)
|
||||||
case npc.West:
|
case model.West:
|
||||||
nextPos = nextPos.WithOffset(-1, 0)
|
nextPos = nextPos.WithOffset(-1, 0)
|
||||||
case npc.East:
|
case model.East:
|
||||||
nextPos = nextPos.WithOffset(+1, 0)
|
nextPos = nextPos.WithOffset(+1, 0)
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
|
@ -320,23 +363,12 @@ func (ps *PlayingState) CalcPathToPlayerAndMove() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ps.PlayerWithinHitRange(ps.someNPC.Position()) {
|
if ps.PlayerWithinHitRange(ps.someNPC.Positioned().Position) {
|
||||||
hit, precision, evasion, dmg, dmgType := ps.player.CalculateAttack(ps.player)
|
ExecuteAttack(ps.eventLog, ps.someNPC, ps.player)
|
||||||
|
|
||||||
if !hit {
|
|
||||||
ps.eventLog.Log(fmt.Sprintf("%v attacked you, but missed ( %v Evasion vs %v Precision)", ps.someNPC.Name(), evasion, precision))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ps.player.Damage(dmg)
|
|
||||||
ps.healthBar.SetHealth(ps.player.CurrentHealth())
|
|
||||||
ps.eventLog.Log(fmt.Sprintf("%v attacked you, and hit for %v %v damage", ps.someNPC.Name(), dmg, rpg.DamageTypeName(dmgType)))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pathToPlayer := engine.FindPath(
|
pathToPlayer := engine.FindPath(
|
||||||
ps.someNPC.Position(),
|
ps.someNPC.Positioned().Position,
|
||||||
ps.player.Position(),
|
ps.player.Position(),
|
||||||
func(x, y int) bool {
|
func(x, y int) bool {
|
||||||
if x == ps.player.Position().X() && y == ps.player.Position().Y() {
|
if x == ps.player.Position().X() && y == ps.player.Position().Y() {
|
||||||
|
@ -371,13 +403,13 @@ func (ps *PlayingState) OnTick(dt int64) (nextState GameState) {
|
||||||
func (ps *PlayingState) CollectDrawables() []engine.Drawable {
|
func (ps *PlayingState) CollectDrawables() []engine.Drawable {
|
||||||
mainCameraDrawingInstructions := engine.CreateDrawingInstructions(func(v views.View) {
|
mainCameraDrawingInstructions := engine.CreateDrawingInstructions(func(v views.View) {
|
||||||
visibilityMap := engine.ComputeFOV(
|
visibilityMap := engine.ComputeFOV(
|
||||||
func(x, y int) world.Tile {
|
func(x, y int) model.Tile_V2 {
|
||||||
ps.dungeon.CurrentLevel().Flatten().MarkExplored(x, y)
|
model.Map_MarkExplored(ps.dungeon.CurrentLevel().Ground(), x, y)
|
||||||
|
|
||||||
return ps.dungeon.CurrentLevel().TileAt(x, y)
|
return ps.dungeon.CurrentLevel().TileAt(x, y)
|
||||||
},
|
},
|
||||||
func(x, y int) bool { return ps.dungeon.CurrentLevel().Flatten().IsInBounds(x, y) },
|
func(x, y int) bool { return model.Map_IsInBounds(ps.dungeon.CurrentLevel().Ground(), x, y) },
|
||||||
func(x, y int) bool { return ps.dungeon.CurrentLevel().Flatten().TileAt(x, y).Opaque() },
|
func(x, y int) bool { return ps.dungeon.CurrentLevel().TileAt(x, y).Opaque() },
|
||||||
ps.player.Position().X(), ps.player.Position().Y(),
|
ps.player.Position().X(), ps.player.Position().Y(),
|
||||||
13,
|
13,
|
||||||
)
|
)
|
||||||
|
@ -386,13 +418,21 @@ func (ps *PlayingState) CollectDrawables() []engine.Drawable {
|
||||||
tile := visibilityMap[engine.PositionAt(x, y)]
|
tile := visibilityMap[engine.PositionAt(x, y)]
|
||||||
|
|
||||||
if tile != nil {
|
if tile != nil {
|
||||||
return tile.Presentation()
|
if tile.Entity() != nil {
|
||||||
|
return tile.Entity().Entity.Presentable().Rune, tile.Entity().Entity.Presentable().Style
|
||||||
}
|
}
|
||||||
|
|
||||||
explored := ps.dungeon.CurrentLevel().Flatten().ExploredTileAt(x, y)
|
if tile.Item() != nil {
|
||||||
|
return tile.Item().Item.TileIcon(), tile.Item().Item.Style()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tile.DefaultPresentation()
|
||||||
|
}
|
||||||
|
|
||||||
|
explored := model.Map_ExploredTileAt(ps.dungeon.CurrentLevel().Ground(), x, y)
|
||||||
|
|
||||||
if explored != nil {
|
if explored != nil {
|
||||||
return explored.Presentation()
|
return explored.DefaultPresentation()
|
||||||
}
|
}
|
||||||
|
|
||||||
return ' ', tcell.StyleDefault
|
return ' ', tcell.StyleDefault
|
||||||
|
|
|
@ -2,14 +2,14 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/systems"
|
||||||
)
|
)
|
||||||
|
|
||||||
type QuitState struct {
|
type QuitState struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *QuitState) InputContext() input.Context {
|
func (s *QuitState) InputContext() systems.InputContext {
|
||||||
return input.InputContext_Menu
|
return systems.InputContext_Menu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *QuitState) OnTick(dt int64) GameState {
|
func (q *QuitState) OnTick(dt int64) GameState {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package input
|
package systems
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Context string
|
type InputContext string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
InputContext_Play = "play"
|
InputContext_Play = "play"
|
||||||
|
@ -16,7 +16,7 @@ const (
|
||||||
|
|
||||||
type InputKey string
|
type InputKey string
|
||||||
|
|
||||||
func InputKeyOf(context Context, mod tcell.ModMask, key tcell.Key, r rune) InputKey {
|
func InputKeyOf(context InputContext, mod tcell.ModMask, key tcell.Key, r rune) InputKey {
|
||||||
return InputKey(fmt.Sprintf("%v-%v-%v-%v", context, mod, key, r))
|
return InputKey(fmt.Sprintf("%v-%v-%v-%v", context, mod, key, r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ func (kb *InputSystem) Bind(key InputKey, action InputAction) {
|
||||||
kb.keyBindings[key] = action
|
kb.keyBindings[key] = action
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kb *InputSystem) Input(context Context, ev *tcell.EventKey) {
|
func (kb *InputSystem) Input(context InputContext, ev *tcell.EventKey) {
|
||||||
kb.nextAction = kb.keyBindings[InputKeyOf(context, ev.Modifiers(), ev.Key(), ev.Rune())]
|
kb.nextAction = kb.keyBindings[InputKeyOf(context, ev.Modifiers(), ev.Key(), ev.Rune())]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package turns
|
package systems
|
||||||
|
|
||||||
import "mvvasilev/last_light/engine"
|
import "mvvasilev/last_light/engine"
|
||||||
|
|
166
game/ui/item.go
166
game/ui/item.go
|
@ -1,166 +0,0 @@
|
||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
"mvvasilev/last_light/game/input"
|
|
||||||
"mvvasilev/last_light/game/item"
|
|
||||||
"mvvasilev/last_light/game/rpg"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/gdamore/tcell/v2/views"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UIBasicItem struct {
|
|
||||||
id uuid.UUID
|
|
||||||
|
|
||||||
item item.Item
|
|
||||||
|
|
||||||
window UIWindow
|
|
||||||
itemName UILabel
|
|
||||||
|
|
||||||
engine.Positioned
|
|
||||||
engine.Sized
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateUIBasicItem(x, y int, item item.Item, style tcell.Style) *UIBasicItem {
|
|
||||||
|
|
||||||
name, nameStyle := item.Name()
|
|
||||||
|
|
||||||
return &UIBasicItem{
|
|
||||||
id: uuid.New(),
|
|
||||||
item: item,
|
|
||||||
window: *CreateWindow(x, y, 33, 8, "Item", style),
|
|
||||||
itemName: *CreateSingleLineUILabel(x+1, y+1, name, nameStyle),
|
|
||||||
Positioned: engine.WithPosition(engine.PositionAt(x, y)),
|
|
||||||
Sized: engine.WithSize(engine.SizeOf(33, 8)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uibi *UIBasicItem) Input(e *tcell.EventKey) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uibi *UIBasicItem) UniqueId() uuid.UUID {
|
|
||||||
return uibi.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uibi *UIBasicItem) Draw(v views.View) {
|
|
||||||
uibi.window.Draw(v)
|
|
||||||
uibi.itemName.Draw(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UIRPGItem struct {
|
|
||||||
id uuid.UUID
|
|
||||||
|
|
||||||
item rpg.RPGItem
|
|
||||||
|
|
||||||
window UIWindow
|
|
||||||
itemName UILabel
|
|
||||||
|
|
||||||
engine.Positioned
|
|
||||||
engine.Sized
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateUIRPGItem(x, y int, item rpg.RPGItem, style tcell.Style) *UIRPGItem {
|
|
||||||
|
|
||||||
name, nameStyle := item.Name()
|
|
||||||
|
|
||||||
return &UIRPGItem{
|
|
||||||
id: uuid.New(),
|
|
||||||
item: item,
|
|
||||||
window: *CreateWindow(x, y, 33, 8, "Item", style),
|
|
||||||
itemName: *CreateSingleLineUILabel(x+1, y+1, name, nameStyle),
|
|
||||||
Positioned: engine.WithPosition(engine.PositionAt(x, y)),
|
|
||||||
Sized: engine.WithSize(engine.SizeOf(33, 8)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uiri *UIRPGItem) Input(inputAction input.InputAction) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uiri *UIRPGItem) UniqueId() uuid.UUID {
|
|
||||||
return uiri.id
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uiri *UIRPGItem) Draw(v views.View) {
|
|
||||||
uiri.window.Draw(v)
|
|
||||||
uiri.itemName.Draw(v)
|
|
||||||
|
|
||||||
statModifiers := uiri.item.Modifiers()
|
|
||||||
|
|
||||||
x, y := uiri.itemName.Position().XY()
|
|
||||||
y++
|
|
||||||
|
|
||||||
for i, sm := range statModifiers {
|
|
||||||
|
|
||||||
drawRPGItemStatModifier(x, y, tcell.StyleDefault, v, &sm)
|
|
||||||
|
|
||||||
x += 9 + 2 // each stat is 9 characters long, with 2 characters separating the stats
|
|
||||||
|
|
||||||
// Only 3 stats per line
|
|
||||||
if i > 0 && (i+1)%3 == 0 {
|
|
||||||
x = uiri.itemName.Position().X()
|
|
||||||
y++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func drawRPGItemStatModifier(x, y int, style tcell.Style, view views.View, sm *rpg.StatModifier) {
|
|
||||||
|
|
||||||
// 5 characters per stat name
|
|
||||||
// 1 separating character
|
|
||||||
// 3 characters for bonus ( including sign, modifiers are limited to -99 and +99)
|
|
||||||
|
|
||||||
const SEPARATING_CHARACTER rune = ':'
|
|
||||||
|
|
||||||
switch sm.Stat {
|
|
||||||
case rpg.Stat_Attributes_Strength:
|
|
||||||
engine.DrawText(x, y, "STR", style, view)
|
|
||||||
case rpg.Stat_Attributes_Dexterity:
|
|
||||||
engine.DrawText(x, y, "DEX", style, view)
|
|
||||||
case rpg.Stat_Attributes_Intelligence:
|
|
||||||
engine.DrawText(x, y, "INT", style, view)
|
|
||||||
case rpg.Stat_Attributes_Constitution:
|
|
||||||
engine.DrawText(x, y, "CON", style, view)
|
|
||||||
case rpg.Stat_PhysicalPrecisionBonus:
|
|
||||||
engine.DrawText(x, y, "pPrcs", style, view)
|
|
||||||
case rpg.Stat_EvasionBonus:
|
|
||||||
engine.DrawText(x, y, "Evasn", style, view)
|
|
||||||
case rpg.Stat_MagicPrecisionBonus:
|
|
||||||
engine.DrawText(x, y, "mPrcs", style, view)
|
|
||||||
case rpg.Stat_TotalPrecisionBonus:
|
|
||||||
engine.DrawText(x, y, "tPrcs", style, view)
|
|
||||||
case rpg.Stat_DamageBonus_Physical_Unarmed:
|
|
||||||
engine.DrawText(x, y, "Unrmd", style, view)
|
|
||||||
case rpg.Stat_DamageBonus_Physical_Slashing:
|
|
||||||
engine.DrawText(x, y, "Slshn", style, view)
|
|
||||||
case rpg.Stat_DamageBonus_Physical_Piercing:
|
|
||||||
engine.DrawText(x, y, "Prcng", style, view)
|
|
||||||
case rpg.Stat_DamageBonus_Physical_Bludgeoning:
|
|
||||||
engine.DrawText(x, y, "Bldgn", style, view)
|
|
||||||
case rpg.Stat_DamageBonus_Magic_Fire:
|
|
||||||
engine.DrawText(x, y, "Fire", style, view)
|
|
||||||
case rpg.Stat_DamageBonus_Magic_Cold:
|
|
||||||
engine.DrawText(x, y, "Cold", style, view)
|
|
||||||
case rpg.Stat_DamageBonus_Magic_Necrotic:
|
|
||||||
engine.DrawText(x, y, "Ncrtc", style, view)
|
|
||||||
case rpg.Stat_DamageBonus_Magic_Thunder:
|
|
||||||
engine.DrawText(x, y, "Thndr", style, view)
|
|
||||||
case rpg.Stat_DamageBonus_Magic_Acid:
|
|
||||||
engine.DrawText(x, y, "Acid", style, view)
|
|
||||||
case rpg.Stat_DamageBonus_Magic_Poison:
|
|
||||||
engine.DrawText(x, y, "Poisn", style, view)
|
|
||||||
case rpg.Stat_MaxHealthBonus:
|
|
||||||
engine.DrawText(x, y, "maxHP", style, view)
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
view.SetContent(x+5, y, SEPARATING_CHARACTER, nil, style)
|
|
||||||
|
|
||||||
if sm.Bonus < 0 {
|
|
||||||
engine.DrawText(x+6, y, fmt.Sprintf("-%02d", -sm.Bonus), tcell.StyleDefault.Foreground(tcell.ColorIndianRed), view)
|
|
||||||
} else {
|
|
||||||
engine.DrawText(x+6, y, fmt.Sprintf("+%02d", sm.Bonus), tcell.StyleDefault.Foreground(tcell.ColorLime), view)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,8 +3,8 @@ package menu
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/model"
|
||||||
"mvvasilev/last_light/game/rpg"
|
"mvvasilev/last_light/game/systems"
|
||||||
"mvvasilev/last_light/game/ui"
|
"mvvasilev/last_light/game/ui"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -13,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type statSelection struct {
|
type statSelection struct {
|
||||||
stat rpg.Stat
|
stat model.Stat
|
||||||
label *ui.UILabel
|
label *ui.UILabel
|
||||||
plusButton *ui.UILabel
|
plusButton *ui.UILabel
|
||||||
statNumberLabel *ui.UILabel
|
statNumberLabel *ui.UILabel
|
||||||
|
@ -21,7 +21,7 @@ type statSelection struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type StatState struct {
|
type StatState struct {
|
||||||
Stat rpg.Stat
|
Stat model.Stat
|
||||||
Value int
|
Value int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ func (ccm *CharacterCreationMenu) UpdateState(state *CharacterCreationMenuState)
|
||||||
label: ui.CreateSingleLineUILabel(
|
label: ui.CreateSingleLineUILabel(
|
||||||
statX,
|
statX,
|
||||||
3+i,
|
3+i,
|
||||||
rpg.StatLongName(s.Stat),
|
model.StatLongName(s.Stat),
|
||||||
labelStyle,
|
labelStyle,
|
||||||
),
|
),
|
||||||
minusButton: ui.CreateSingleLineUILabel(
|
minusButton: ui.CreateSingleLineUILabel(
|
||||||
|
@ -172,7 +172,7 @@ func (ccm *CharacterCreationMenu) Size() engine.Size {
|
||||||
return engine.SizeOf(engine.TERMINAL_SIZE_WIDTH, engine.TERMINAL_SIZE_HEIGHT)
|
return engine.SizeOf(engine.TERMINAL_SIZE_WIDTH, engine.TERMINAL_SIZE_HEIGHT)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ccm *CharacterCreationMenu) Input(inputAction input.InputAction) {
|
func (ccm *CharacterCreationMenu) Input(inputAction systems.InputAction) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,8 @@ package menu
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/model"
|
||||||
"mvvasilev/last_light/game/item"
|
"mvvasilev/last_light/game/systems"
|
||||||
"mvvasilev/last_light/game/rpg"
|
|
||||||
"mvvasilev/last_light/game/ui"
|
"mvvasilev/last_light/game/ui"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -14,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type PlayerInventoryMenu struct {
|
type PlayerInventoryMenu struct {
|
||||||
inventory *item.EquippedInventory
|
inventory *model.EquippedInventory
|
||||||
|
|
||||||
inventoryMenu *ui.UIWindow
|
inventoryMenu *ui.UIWindow
|
||||||
armourLabel *ui.UILabel
|
armourLabel *ui.UILabel
|
||||||
|
@ -26,12 +25,11 @@ type PlayerInventoryMenu struct {
|
||||||
inventoryGrid *engine.Grid
|
inventoryGrid *engine.Grid
|
||||||
playerItems *engine.ArbitraryDrawable
|
playerItems *engine.ArbitraryDrawable
|
||||||
selectedItem *engine.ArbitraryDrawable
|
selectedItem *engine.ArbitraryDrawable
|
||||||
help *ui.UILabel
|
|
||||||
|
|
||||||
selectedInventorySlot engine.Position
|
selectedInventorySlot engine.Position
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreatePlayerInventoryMenu(x, y int, playerInventory *item.EquippedInventory, style tcell.Style, highlightStyle tcell.Style) *PlayerInventoryMenu {
|
func CreatePlayerInventoryMenu(x, y int, playerInventory *model.EquippedInventory, style tcell.Style, highlightStyle tcell.Style) *PlayerInventoryMenu {
|
||||||
menu := new(PlayerInventoryMenu)
|
menu := new(PlayerInventoryMenu)
|
||||||
|
|
||||||
menu.inventory = playerInventory
|
menu.inventory = playerInventory
|
||||||
|
@ -94,25 +92,19 @@ func CreatePlayerInventoryMenu(x, y int, playerInventory *item.EquippedInventory
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
style := item.Type().Style()
|
style := item.Style()
|
||||||
|
|
||||||
if isHighlighted {
|
if isHighlighted {
|
||||||
style = highlightStyle
|
style = highlightStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.CreateSingleLineUILabel(
|
menu.drawItemSlot(
|
||||||
menu.inventoryGrid.Position().X()+1+x*4,
|
menu.inventoryGrid.Position().X()+1+x*4,
|
||||||
menu.inventoryGrid.Position().Y()+y*2,
|
menu.inventoryGrid.Position().Y()+y*2,
|
||||||
fmt.Sprintf("%03d", item.Quantity()),
|
item,
|
||||||
style,
|
style,
|
||||||
).Draw(v)
|
v,
|
||||||
|
)
|
||||||
ui.CreateSingleLineUILabel(
|
|
||||||
menu.inventoryGrid.Position().X()+1+x*4,
|
|
||||||
menu.inventoryGrid.Position().Y()+1+y*2,
|
|
||||||
item.Type().Icon(),
|
|
||||||
style,
|
|
||||||
).Draw(v)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -124,19 +116,30 @@ func CreatePlayerInventoryMenu(x, y int, playerInventory *item.EquippedInventory
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch it := item.(type) {
|
ui.CreateUIItem(x+2, y+14, item, style).Draw(v)
|
||||||
case rpg.RPGItem:
|
|
||||||
ui.CreateUIRPGItem(x+2, y+14, it, style).Draw(v)
|
|
||||||
default:
|
|
||||||
ui.CreateUIBasicItem(x+2, y+14, it, style).Draw(v)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
menu.help = ui.CreateSingleLineUILabel(x+2, y+22, "hjkl - move, x - drop, e - equip", style)
|
|
||||||
|
|
||||||
return menu
|
return menu
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pim *PlayerInventoryMenu) drawItemSlot(screenX, screenY int, item model.Item_V2, style tcell.Style, v views.View) {
|
||||||
|
if item.Quantifiable() != nil {
|
||||||
|
ui.CreateSingleLineUILabel(
|
||||||
|
screenX,
|
||||||
|
screenY,
|
||||||
|
fmt.Sprintf("%03d", item.Quantifiable().CurrentQuantity),
|
||||||
|
style,
|
||||||
|
).Draw(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.CreateSingleLineUILabel(
|
||||||
|
screenX,
|
||||||
|
screenY+1,
|
||||||
|
item.Icon(),
|
||||||
|
style,
|
||||||
|
).Draw(v)
|
||||||
|
}
|
||||||
|
|
||||||
func (pim *PlayerInventoryMenu) MoveTo(x int, y int) {
|
func (pim *PlayerInventoryMenu) MoveTo(x int, y int) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -149,7 +152,7 @@ func (pim *PlayerInventoryMenu) Size() engine.Size {
|
||||||
return pim.inventoryMenu.Size()
|
return pim.inventoryMenu.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pim *PlayerInventoryMenu) Input(inputAction input.InputAction) {
|
func (pim *PlayerInventoryMenu) Input(inputAction systems.InputAction) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,5 +181,4 @@ func (pim *PlayerInventoryMenu) Draw(v views.View) {
|
||||||
pim.inventoryGrid.Draw(v)
|
pim.inventoryGrid.Draw(v)
|
||||||
pim.playerItems.Draw(v)
|
pim.playerItems.Draw(v)
|
||||||
pim.selectedItem.Draw(v)
|
pim.selectedItem.Draw(v)
|
||||||
pim.help.Draw(v)
|
|
||||||
}
|
}
|
|
@ -2,14 +2,14 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/systems"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UIElement interface {
|
type UIElement interface {
|
||||||
MoveTo(x, y int)
|
MoveTo(x, y int)
|
||||||
Position() engine.Position
|
Position() engine.Position
|
||||||
Size() engine.Size
|
Size() engine.Size
|
||||||
Input(inputAction input.InputAction)
|
Input(inputAction systems.InputAction)
|
||||||
|
|
||||||
engine.Drawable
|
engine.Drawable
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/systems"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/gdamore/tcell/v2/views"
|
"github.com/gdamore/tcell/v2/views"
|
||||||
|
@ -91,13 +91,13 @@ func (d *UIDialog) Size() engine.Size {
|
||||||
return d.window.Size()
|
return d.window.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *UIDialog) Input(inputAction input.InputAction) {
|
func (d *UIDialog) Input(inputAction systems.InputAction) {
|
||||||
if inputAction == input.InputAction_Menu_HighlightLeft {
|
if inputAction == systems.InputAction_Menu_HighlightLeft {
|
||||||
if !d.yesBtn.IsHighlighted() {
|
if !d.yesBtn.IsHighlighted() {
|
||||||
d.noBtn.Unhighlight()
|
d.noBtn.Unhighlight()
|
||||||
d.yesBtn.Highlight()
|
d.yesBtn.Highlight()
|
||||||
}
|
}
|
||||||
} else if inputAction == input.InputAction_Menu_HighlightRight {
|
} else if inputAction == systems.InputAction_Menu_HighlightRight {
|
||||||
if d.noBtn == nil {
|
if d.noBtn == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/systems"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/gdamore/tcell/v2/views"
|
"github.com/gdamore/tcell/v2/views"
|
||||||
|
@ -40,7 +40,7 @@ func (uie *UIEventLog) Size() engine.Size {
|
||||||
return uie.window.Size()
|
return uie.window.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uie *UIEventLog) Input(inputAction input.InputAction) {
|
func (uie *UIEventLog) Input(inputAction systems.InputAction) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/model"
|
||||||
|
"mvvasilev/last_light/game/systems"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/gdamore/tcell/v2/views"
|
"github.com/gdamore/tcell/v2/views"
|
||||||
|
@ -13,34 +14,21 @@ import (
|
||||||
|
|
||||||
type UIHealthBar struct {
|
type UIHealthBar struct {
|
||||||
id uuid.UUID
|
id uuid.UUID
|
||||||
health int
|
player *model.Player_V2
|
||||||
maxHealth int
|
|
||||||
|
|
||||||
window *UIWindow
|
window *UIWindow
|
||||||
|
|
||||||
style tcell.Style
|
style tcell.Style
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: style for health bar fill
|
func CreateHealthBar(x, y, w, h int, player *model.Player_V2, style tcell.Style) *UIHealthBar {
|
||||||
// TODO: 'HP' title
|
|
||||||
// TODO: test different percentages
|
|
||||||
func CreateHealthBar(x, y, w, h, health, maxHealth int, style tcell.Style) *UIHealthBar {
|
|
||||||
return &UIHealthBar{
|
return &UIHealthBar{
|
||||||
window: CreateWindow(x, y, w, h, "HP", style),
|
window: CreateWindow(x, y, w, h, "HP", style),
|
||||||
health: health,
|
player: player,
|
||||||
maxHealth: maxHealth,
|
|
||||||
style: style,
|
style: style,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uihp *UIHealthBar) SetHealth(health int) {
|
|
||||||
uihp.health = health
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uihp *UIHealthBar) SetMaxHealth(maxHealth int) {
|
|
||||||
uihp.maxHealth = maxHealth
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uihp *UIHealthBar) MoveTo(x int, y int) {
|
func (uihp *UIHealthBar) MoveTo(x int, y int) {
|
||||||
uihp.window.MoveTo(x, y)
|
uihp.window.MoveTo(x, y)
|
||||||
}
|
}
|
||||||
|
@ -53,7 +41,7 @@ func (uihp *UIHealthBar) Size() engine.Size {
|
||||||
return uihp.window.Size()
|
return uihp.window.Size()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uihp *UIHealthBar) Input(inputAction input.InputAction) {
|
func (uihp *UIHealthBar) Input(inputAction systems.InputAction) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uihp *UIHealthBar) UniqueId() uuid.UUID {
|
func (uihp *UIHealthBar) UniqueId() uuid.UUID {
|
||||||
|
@ -67,7 +55,7 @@ func (uihp *UIHealthBar) Draw(v views.View) {
|
||||||
|
|
||||||
stages := []rune{'█', '▓', '▒', '░'} // 0 = 1.0, 1 = 0.75, 2 = 0.5, 3 = 0.25
|
stages := []rune{'█', '▓', '▒', '░'} // 0 = 1.0, 1 = 0.75, 2 = 0.5, 3 = 0.25
|
||||||
|
|
||||||
percentage := (float64(w) - 2.0) * (float64(uihp.health) / float64(uihp.maxHealth))
|
percentage := (float64(w) - 2.0) * (float64(uihp.player.HealthData().Health) / float64(uihp.player.HealthData().MaxHealth))
|
||||||
|
|
||||||
whole := math.Trunc(percentage)
|
whole := math.Trunc(percentage)
|
||||||
last := percentage - whole
|
last := percentage - whole
|
||||||
|
@ -92,7 +80,7 @@ func (uihp *UIHealthBar) Draw(v views.View) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hpText := fmt.Sprintf("%v/%v", uihp.health, uihp.maxHealth)
|
hpText := fmt.Sprintf("%v/%v", uihp.player.HealthData().Health, uihp.player.HealthData().MaxHealth)
|
||||||
|
|
||||||
engine.DrawText(
|
engine.DrawText(
|
||||||
x+w/2-len(hpText)/2,
|
x+w/2-len(hpText)/2,
|
133
game/ui/ui_item.go
Normal file
133
game/ui/ui_item.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"mvvasilev/last_light/engine"
|
||||||
|
"mvvasilev/last_light/game/model"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/gdamore/tcell/v2/views"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UIItem struct {
|
||||||
|
id uuid.UUID
|
||||||
|
|
||||||
|
item model.Item_V2
|
||||||
|
|
||||||
|
window UIWindow
|
||||||
|
|
||||||
|
engine.Positioned
|
||||||
|
engine.Sized
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateUIItem(x, y int, item model.Item_V2, style tcell.Style) *UIItem {
|
||||||
|
return &UIItem{
|
||||||
|
id: uuid.New(),
|
||||||
|
item: item,
|
||||||
|
window: *CreateWindow(x, y, 33, 8, "Item", style),
|
||||||
|
Positioned: engine.WithPosition(engine.PositionAt(x, y)),
|
||||||
|
Sized: engine.WithSize(engine.SizeOf(33, 8)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uibi *UIItem) Input(e *tcell.EventKey) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uibi *UIItem) UniqueId() uuid.UUID {
|
||||||
|
return uibi.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uibi *UIItem) Draw(v views.View) {
|
||||||
|
uibi.window.Draw(v)
|
||||||
|
|
||||||
|
if uibi.item.Named() != nil {
|
||||||
|
engine.DrawText(uibi.Position().X()+1, uibi.Position().Y()+1, uibi.item.Named().Name, uibi.item.Named().Style, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if uibi.item.Described() != nil {
|
||||||
|
engine.DrawText(uibi.Position().X()+1, uibi.Position().Y()+2, uibi.item.Described().Description, uibi.item.Described().Style, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if uibi.item.StatModifier() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
statModifiers := uibi.item.StatModifier().StatModifiers
|
||||||
|
|
||||||
|
originalX, y := uibi.Position().XY()
|
||||||
|
x := originalX + 1
|
||||||
|
y += 3
|
||||||
|
|
||||||
|
for i, sm := range statModifiers {
|
||||||
|
|
||||||
|
drawRPGItemStatModifier(x, y, tcell.StyleDefault, v, &sm)
|
||||||
|
|
||||||
|
x += 9 + 2 // each stat is 9 characters long, with 2 characters separating the stats
|
||||||
|
|
||||||
|
// Only 3 stats per line
|
||||||
|
if i > 0 && (i+1)%3 == 0 {
|
||||||
|
x = originalX + 1
|
||||||
|
y++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawRPGItemStatModifier(x, y int, style tcell.Style, view views.View, sm *model.StatModifier) {
|
||||||
|
|
||||||
|
// 5 characters per stat name
|
||||||
|
// 1 separating character
|
||||||
|
// 3 characters for bonus ( including sign, modifiers are limited to -99 and +99)
|
||||||
|
|
||||||
|
const SEPARATING_CHARACTER rune = ':'
|
||||||
|
|
||||||
|
switch sm.Stat {
|
||||||
|
case model.Stat_Attributes_Strength:
|
||||||
|
engine.DrawText(x, y, "STR", style, view)
|
||||||
|
case model.Stat_Attributes_Dexterity:
|
||||||
|
engine.DrawText(x, y, "DEX", style, view)
|
||||||
|
case model.Stat_Attributes_Intelligence:
|
||||||
|
engine.DrawText(x, y, "INT", style, view)
|
||||||
|
case model.Stat_Attributes_Constitution:
|
||||||
|
engine.DrawText(x, y, "CON", style, view)
|
||||||
|
case model.Stat_PhysicalPrecisionBonus:
|
||||||
|
engine.DrawText(x, y, "pPrcs", style, view)
|
||||||
|
case model.Stat_EvasionBonus:
|
||||||
|
engine.DrawText(x, y, "Evasn", style, view)
|
||||||
|
case model.Stat_MagicPrecisionBonus:
|
||||||
|
engine.DrawText(x, y, "mPrcs", style, view)
|
||||||
|
case model.Stat_TotalPrecisionBonus:
|
||||||
|
engine.DrawText(x, y, "tPrcs", style, view)
|
||||||
|
case model.Stat_DamageBonus_Physical_Unarmed:
|
||||||
|
engine.DrawText(x, y, "Unrmd", style, view)
|
||||||
|
case model.Stat_DamageBonus_Physical_Slashing:
|
||||||
|
engine.DrawText(x, y, "Slshn", style, view)
|
||||||
|
case model.Stat_DamageBonus_Physical_Piercing:
|
||||||
|
engine.DrawText(x, y, "Prcng", style, view)
|
||||||
|
case model.Stat_DamageBonus_Physical_Bludgeoning:
|
||||||
|
engine.DrawText(x, y, "Bldgn", style, view)
|
||||||
|
case model.Stat_DamageBonus_Magic_Fire:
|
||||||
|
engine.DrawText(x, y, "Fire", style, view)
|
||||||
|
case model.Stat_DamageBonus_Magic_Cold:
|
||||||
|
engine.DrawText(x, y, "Cold", style, view)
|
||||||
|
case model.Stat_DamageBonus_Magic_Necrotic:
|
||||||
|
engine.DrawText(x, y, "Ncrtc", style, view)
|
||||||
|
case model.Stat_DamageBonus_Magic_Thunder:
|
||||||
|
engine.DrawText(x, y, "Thndr", style, view)
|
||||||
|
case model.Stat_DamageBonus_Magic_Acid:
|
||||||
|
engine.DrawText(x, y, "Acid", style, view)
|
||||||
|
case model.Stat_DamageBonus_Magic_Poison:
|
||||||
|
engine.DrawText(x, y, "Poisn", style, view)
|
||||||
|
case model.Stat_MaxHealthBonus:
|
||||||
|
engine.DrawText(x, y, "maxHP", style, view)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
view.SetContent(x+5, y, SEPARATING_CHARACTER, nil, style)
|
||||||
|
|
||||||
|
if sm.Bonus < 0 {
|
||||||
|
engine.DrawText(x+6, y, fmt.Sprintf("-%02d", -sm.Bonus), tcell.StyleDefault.Foreground(tcell.ColorIndianRed), view)
|
||||||
|
} else {
|
||||||
|
engine.DrawText(x+6, y, fmt.Sprintf("+%02d", sm.Bonus), tcell.StyleDefault.Foreground(tcell.ColorLime), view)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/systems"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -57,4 +57,4 @@ func (t *UILabel) Draw(v views.View) {
|
||||||
t.text.Draw(v)
|
t.text.Draw(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *UILabel) Input(inputAction input.InputAction) {}
|
func (t *UILabel) Input(inputAction systems.InputAction) {}
|
|
@ -2,7 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/systems"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
@ -97,6 +97,6 @@ func (sb *UISimpleButton) Draw(v views.View) {
|
||||||
sb.text.Draw(v)
|
sb.text.Draw(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb *UISimpleButton) Input(inputAction input.InputAction) {
|
func (sb *UISimpleButton) Input(inputAction systems.InputAction) {
|
||||||
|
|
||||||
}
|
}
|
|
@ -2,7 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"mvvasilev/last_light/engine"
|
"mvvasilev/last_light/engine"
|
||||||
"mvvasilev/last_light/game/input"
|
"mvvasilev/last_light/game/systems"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
|
@ -63,5 +63,5 @@ func (w *UIWindow) Draw(v views.View) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *UIWindow) Input(inputAction input.InputAction) {
|
func (w *UIWindow) Input(inputAction systems.InputAction) {
|
||||||
}
|
}
|
|
@ -1,56 +0,0 @@
|
||||||
package world
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BSPDungeonMap struct {
|
|
||||||
level *BasicMap
|
|
||||||
|
|
||||||
playerSpawnPoint engine.Position
|
|
||||||
nextLevelStaircase engine.Position
|
|
||||||
rooms []engine.BoundingBox
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonMap) PlayerSpawnPoint() engine.Position {
|
|
||||||
return bsp.playerSpawnPoint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonMap) NextLevelStaircasePosition() engine.Position {
|
|
||||||
return bsp.nextLevelStaircase
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonMap) Size() engine.Size {
|
|
||||||
return bsp.level.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonMap) SetTileAt(x int, y int, t Tile) Tile {
|
|
||||||
return bsp.level.SetTileAt(x, y, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonMap) TileAt(x int, y int) Tile {
|
|
||||||
return bsp.level.TileAt(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonMap) IsInBounds(x, y int) bool {
|
|
||||||
return bsp.level.IsInBounds(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonMap) ExploredTileAt(x, y int) Tile {
|
|
||||||
return bsp.level.ExploredTileAt(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonMap) MarkExplored(x, y int) {
|
|
||||||
bsp.level.MarkExplored(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonMap) Tick(dt int64) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonMap) Rooms() []engine.BoundingBox {
|
|
||||||
return bsp.rooms
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *BSPDungeonMap) PreviousLevelStaircasePosition() engine.Position {
|
|
||||||
return bsp.playerSpawnPoint
|
|
||||||
}
|
|
|
@ -1,307 +0,0 @@
|
||||||
package world
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
"mvvasilev/last_light/game/item"
|
|
||||||
"mvvasilev/last_light/game/npc"
|
|
||||||
"mvvasilev/last_light/game/rpg"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DungeonType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
DungeonTypeBSP DungeonType = iota
|
|
||||||
DungeonTypeCaverns
|
|
||||||
DungeonTypeMine
|
|
||||||
DungeonTypeUndercity
|
|
||||||
)
|
|
||||||
|
|
||||||
func randomDungeonType() DungeonType {
|
|
||||||
return DungeonType(rand.Intn(4))
|
|
||||||
}
|
|
||||||
|
|
||||||
type Dungeon struct {
|
|
||||||
levels []*DungeonLevel
|
|
||||||
|
|
||||||
current int
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateDungeon(width, height int, depth int) *Dungeon {
|
|
||||||
levels := make([]*DungeonLevel, 0, depth)
|
|
||||||
|
|
||||||
for range depth {
|
|
||||||
levels = append(levels, CreateDungeonLevel(width, height, randomDungeonType()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Dungeon{
|
|
||||||
levels: levels,
|
|
||||||
current: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dungeon) CurrentLevel() *DungeonLevel {
|
|
||||||
return d.levels[d.current]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dungeon) MoveToNextLevel() (moved bool) {
|
|
||||||
if !d.HasNextLevel() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
d.current++
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dungeon) MoveToPreviousLevel() (moved bool) {
|
|
||||||
if !d.HasPreviousLevel() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
d.current--
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dungeon) NextLevel() *DungeonLevel {
|
|
||||||
if !d.HasNextLevel() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.levels[d.current+1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dungeon) PreviousLevel() *DungeonLevel {
|
|
||||||
if !d.HasPreviousLevel() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.levels[d.current-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dungeon) HasPreviousLevel() bool {
|
|
||||||
return d.current-1 >= 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dungeon) HasNextLevel() bool {
|
|
||||||
return d.current+1 < len(d.levels)
|
|
||||||
}
|
|
||||||
|
|
||||||
type DungeonLevel struct {
|
|
||||||
groundLevel interface {
|
|
||||||
Map
|
|
||||||
WithPlayerSpawnPoint
|
|
||||||
WithNextLevelStaircasePosition
|
|
||||||
WithPreviousLevelStaircasePosition
|
|
||||||
}
|
|
||||||
entityLevel *EntityMap
|
|
||||||
itemLevel Map
|
|
||||||
|
|
||||||
multilevel Map
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateDungeonLevel(width, height int, dungeonType DungeonType) *DungeonLevel {
|
|
||||||
|
|
||||||
genTable := rpg.CreateLootTable()
|
|
||||||
|
|
||||||
genTable.Add(10, func() item.Item {
|
|
||||||
return item.CreateBasicItem(item.ItemTypeFish(), 1)
|
|
||||||
})
|
|
||||||
|
|
||||||
itemTypes := []rpg.RPGItemType{
|
|
||||||
rpg.ItemTypeBow(),
|
|
||||||
rpg.ItemTypeLongsword(),
|
|
||||||
rpg.ItemTypeClub(),
|
|
||||||
rpg.ItemTypeDagger(),
|
|
||||||
rpg.ItemTypeHandaxe(),
|
|
||||||
rpg.ItemTypeJavelin(),
|
|
||||||
rpg.ItemTypeLightHammer(),
|
|
||||||
rpg.ItemTypeMace(),
|
|
||||||
rpg.ItemTypeQuarterstaff(),
|
|
||||||
rpg.ItemTypeSickle(),
|
|
||||||
rpg.ItemTypeSpear(),
|
|
||||||
}
|
|
||||||
|
|
||||||
genTable.Add(1, func() item.Item {
|
|
||||||
itemType := itemTypes[rand.Intn(len(itemTypes))]
|
|
||||||
|
|
||||||
rarities := []rpg.ItemRarity{
|
|
||||||
rpg.ItemRarity_Common,
|
|
||||||
rpg.ItemRarity_Uncommon,
|
|
||||||
rpg.ItemRarity_Rare,
|
|
||||||
rpg.ItemRarity_Epic,
|
|
||||||
rpg.ItemRarity_Legendary,
|
|
||||||
}
|
|
||||||
|
|
||||||
return rpg.GenerateItemOfTypeAndRarity(itemType, rarities[rand.Intn(len(rarities))])
|
|
||||||
})
|
|
||||||
|
|
||||||
var groundLevel interface {
|
|
||||||
Map
|
|
||||||
WithRooms
|
|
||||||
WithPlayerSpawnPoint
|
|
||||||
WithNextLevelStaircasePosition
|
|
||||||
WithPreviousLevelStaircasePosition
|
|
||||||
}
|
|
||||||
|
|
||||||
switch dungeonType {
|
|
||||||
case DungeonTypeBSP:
|
|
||||||
groundLevel = CreateBSPDungeonMap(width, height, 4)
|
|
||||||
default:
|
|
||||||
groundLevel = CreateBSPDungeonMap(width, height, 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
items := SpawnItems(groundLevel.Rooms(), 0.1, genTable, []engine.Position{
|
|
||||||
groundLevel.NextLevelStaircasePosition(),
|
|
||||||
groundLevel.PlayerSpawnPoint(),
|
|
||||||
groundLevel.PreviousLevelStaircasePosition(),
|
|
||||||
})
|
|
||||||
|
|
||||||
itemLevel := CreateEmptyDungeonLevel(width, height)
|
|
||||||
|
|
||||||
for _, it := range items {
|
|
||||||
if !groundLevel.TileAt(it.Position().XY()).Passable() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
itemLevel.SetTileAt(it.Position().X(), it.Position().Y(), it)
|
|
||||||
}
|
|
||||||
|
|
||||||
d := &DungeonLevel{
|
|
||||||
groundLevel: groundLevel,
|
|
||||||
entityLevel: CreateEntityMap(width, height),
|
|
||||||
itemLevel: itemLevel,
|
|
||||||
}
|
|
||||||
|
|
||||||
d.multilevel = CreateMultilevelMap(
|
|
||||||
d.groundLevel,
|
|
||||||
d.itemLevel,
|
|
||||||
d.entityLevel,
|
|
||||||
)
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func SpawnItems(spawnableAreas []engine.BoundingBox, maxItemRatio float32, genTable *rpg.LootTable, forbiddenPositions []engine.Position) []Tile {
|
|
||||||
rooms := spawnableAreas
|
|
||||||
|
|
||||||
itemTiles := make([]Tile, 0, 10)
|
|
||||||
|
|
||||||
for _, r := range rooms {
|
|
||||||
maxItems := int(maxItemRatio * float32(r.Size().Area()))
|
|
||||||
|
|
||||||
if maxItems < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
numItems := rand.Intn(maxItems)
|
|
||||||
|
|
||||||
for range numItems {
|
|
||||||
item := genTable.Generate()
|
|
||||||
|
|
||||||
if item == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
pos := engine.PositionAt(
|
|
||||||
engine.RandInt(r.Position().X()+1, r.Position().X()+r.Size().Width()-1),
|
|
||||||
engine.RandInt(r.Position().Y()+1, r.Position().Y()+r.Size().Height()-1),
|
|
||||||
)
|
|
||||||
|
|
||||||
if slices.Contains(forbiddenPositions, pos) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
itemTiles = append(itemTiles, CreateItemTile(pos, item))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return itemTiles
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) PlayerSpawnPoint() engine.Position {
|
|
||||||
return d.groundLevel.PlayerSpawnPoint()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) NextLevelStaircase() engine.Position {
|
|
||||||
return d.groundLevel.NextLevelStaircasePosition()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) PreviousLevelStaircase() engine.Position {
|
|
||||||
return d.groundLevel.PreviousLevelStaircasePosition()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) DropEntity(uuid uuid.UUID) {
|
|
||||||
d.entityLevel.DropEntity(uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) AddEntity(entity npc.MovableEntity) {
|
|
||||||
d.entityLevel.AddEntity(entity)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) MoveEntity(uuid uuid.UUID, dx, dy int) {
|
|
||||||
d.entityLevel.MoveEntity(uuid, dx, dy)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) MoveEntityTo(uuid uuid.UUID, x, y int) {
|
|
||||||
d.entityLevel.MoveEntityTo(uuid, x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) RemoveItemAt(x, y int) item.Item {
|
|
||||||
if !d.groundLevel.Size().Contains(x, y) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tile := d.itemLevel.TileAt(x, y)
|
|
||||||
itemTile, ok := tile.(*ItemTile)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
d.itemLevel.SetTileAt(x, y, nil)
|
|
||||||
|
|
||||||
return itemTile.Item()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) SetItemAt(x, y int, it item.Item) (success bool) {
|
|
||||||
if !d.TileAt(x, y).Passable() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
d.itemLevel.SetTileAt(x, y, CreateItemTile(engine.PositionAt(x, y), it))
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) TileAt(x, y int) Tile {
|
|
||||||
return d.multilevel.TileAt(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) IsTilePassable(x, y int) bool {
|
|
||||||
if !d.groundLevel.Size().Contains(x, y) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.TileAt(x, y).Passable()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) EntityAt(x, y int) (e npc.MovableEntity) {
|
|
||||||
return d.entityLevel.EntityAt(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) IsGroundTileOpaque(x, y int) bool {
|
|
||||||
if !d.groundLevel.Size().Contains(x, y) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.TileAt(x, y).Opaque()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DungeonLevel) Flatten() Map {
|
|
||||||
return d.multilevel
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
package world
|
|
||||||
|
|
||||||
import "mvvasilev/last_light/engine"
|
|
||||||
|
|
||||||
type EmptyDungeonMap struct {
|
|
||||||
level *BasicMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonMap) Size() engine.Size {
|
|
||||||
return edl.level.Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonMap) SetTileAt(x int, y int, t Tile) Tile {
|
|
||||||
return edl.level.SetTileAt(x, y, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonMap) TileAt(x int, y int) Tile {
|
|
||||||
return edl.level.TileAt(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonMap) IsInBounds(x, y int) bool {
|
|
||||||
return edl.level.IsInBounds(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonMap) Tick(dt int64) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonMap) Rooms() []engine.BoundingBox {
|
|
||||||
rooms := make([]engine.BoundingBox, 1)
|
|
||||||
|
|
||||||
rooms = append(rooms, engine.BoundingBox{
|
|
||||||
Sized: engine.WithSize(edl.Size()),
|
|
||||||
Positioned: engine.WithPosition(engine.PositionAt(0, 0)),
|
|
||||||
})
|
|
||||||
|
|
||||||
return rooms
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonMap) PlayerSpawnPoint() engine.Position {
|
|
||||||
return engine.PositionAt(edl.Size().Width()/2, edl.Size().Height()/2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edl *EmptyDungeonMap) NextLevelStaircasePosition() engine.Position {
|
|
||||||
return engine.PositionAt(edl.Size().Width()/3, edl.Size().Height()/3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bsp *EmptyDungeonMap) PreviousLevelStaircasePosition() engine.Position {
|
|
||||||
return bsp.PlayerSpawnPoint()
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
package world
|
|
||||||
|
|
||||||
import (
|
|
||||||
"maps"
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
"mvvasilev/last_light/game/npc"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EntityMap struct {
|
|
||||||
entities map[int]EntityTile
|
|
||||||
|
|
||||||
engine.Sized
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateEntityMap(width, height int) *EntityMap {
|
|
||||||
return &EntityMap{
|
|
||||||
entities: make(map[int]EntityTile, 0),
|
|
||||||
Sized: engine.WithSize(engine.SizeOf(width, height)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) SetTileAt(x int, y int, t Tile) Tile {
|
|
||||||
return nil
|
|
||||||
// if !em.FitsWithin(x, y) {
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// index := em.Size().AsArrayIndex(x, y)
|
|
||||||
|
|
||||||
// TODO? May not be necessary
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) FindEntityByUuid(uuid uuid.UUID) (key int, entity EntityTile) {
|
|
||||||
for i, e := range em.entities {
|
|
||||||
if e.Entity().UniqueId() == uuid {
|
|
||||||
return i, e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) AddEntity(entity npc.MovableEntity) {
|
|
||||||
if !em.FitsWithin(entity.Position().XY()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key := em.Size().AsArrayIndex(entity.Position().XY())
|
|
||||||
et := CreateBasicEntityTile(entity)
|
|
||||||
|
|
||||||
em.entities[key] = et
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) DropEntity(uuid uuid.UUID) {
|
|
||||||
maps.DeleteFunc(em.entities, func(i int, et EntityTile) bool {
|
|
||||||
return et.Entity().UniqueId() == uuid
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) MoveEntity(uuid uuid.UUID, dx, dy int) {
|
|
||||||
oldKey, e := em.FindEntityByUuid(uuid)
|
|
||||||
|
|
||||||
if e == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !em.FitsWithin(e.Entity().Position().WithOffset(dx, dy).XY()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(em.entities, oldKey)
|
|
||||||
|
|
||||||
newPos := e.Entity().Position().WithOffset(dx, dy)
|
|
||||||
e.Entity().MoveTo(newPos)
|
|
||||||
|
|
||||||
newKey := em.Size().AsArrayIndex(e.Entity().Position().XY())
|
|
||||||
|
|
||||||
em.entities[newKey] = e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) MoveEntityTo(uuid uuid.UUID, x, y int) {
|
|
||||||
oldKey, e := em.FindEntityByUuid(uuid)
|
|
||||||
|
|
||||||
if e == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !em.FitsWithin(x, y) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(em.entities, oldKey)
|
|
||||||
|
|
||||||
e.Entity().MoveTo(engine.PositionAt(x, y))
|
|
||||||
|
|
||||||
newKey := em.Size().AsArrayIndex(e.Entity().Position().XY())
|
|
||||||
|
|
||||||
em.entities[newKey] = e
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) TileAt(x int, y int) Tile {
|
|
||||||
if !em.FitsWithin(x, y) {
|
|
||||||
return CreateStaticTile(x, y, TileTypeVoid())
|
|
||||||
}
|
|
||||||
|
|
||||||
key := em.Size().AsArrayIndex(x, y)
|
|
||||||
|
|
||||||
return em.entities[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) EntityAt(x, y int) (ent npc.MovableEntity) {
|
|
||||||
tile := em.TileAt(x, y)
|
|
||||||
|
|
||||||
if tile == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return tile.(EntityTile).Entity()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) IsInBounds(x, y int) bool {
|
|
||||||
return em.FitsWithin(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) MarkExplored(x, y int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) ExploredTileAt(x, y int) Tile {
|
|
||||||
return CreateStaticTile(x, y, TileTypeVoid())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (em *EntityMap) Tick(dt int64) {
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package world
|
|
||||||
|
|
||||||
import "github.com/gdamore/tcell/v2"
|
|
||||||
|
|
||||||
func CreateEmptyDungeonLevel(width, height int) *BasicMap {
|
|
||||||
tiles := make([][]Tile, height)
|
|
||||||
|
|
||||||
for h := range height {
|
|
||||||
tiles[h] = make([]Tile, width)
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateBasicMap(tiles, tcell.StyleDefault.Foreground(tcell.ColorLightSlateGrey))
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
package world
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Map interface {
|
|
||||||
Size() engine.Size
|
|
||||||
SetTileAt(x, y int, t Tile) Tile
|
|
||||||
TileAt(x, y int) Tile
|
|
||||||
IsInBounds(x, y int) bool
|
|
||||||
ExploredTileAt(x, y int) Tile
|
|
||||||
MarkExplored(x, y int)
|
|
||||||
Tick(dt int64)
|
|
||||||
}
|
|
||||||
|
|
||||||
type WithPlayerSpawnPoint interface {
|
|
||||||
PlayerSpawnPoint() engine.Position
|
|
||||||
}
|
|
||||||
|
|
||||||
type WithRooms interface {
|
|
||||||
Rooms() []engine.BoundingBox
|
|
||||||
}
|
|
||||||
|
|
||||||
type WithNextLevelStaircasePosition interface {
|
|
||||||
NextLevelStaircasePosition() engine.Position
|
|
||||||
}
|
|
||||||
|
|
||||||
type WithPreviousLevelStaircasePosition interface {
|
|
||||||
PreviousLevelStaircasePosition() engine.Position
|
|
||||||
}
|
|
||||||
|
|
||||||
type BasicMap struct {
|
|
||||||
tiles [][]Tile
|
|
||||||
exploredTiles map[engine.Position]Tile
|
|
||||||
|
|
||||||
exploredStyle tcell.Style
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateBasicMap(tiles [][]Tile, exploredStyle tcell.Style) *BasicMap {
|
|
||||||
bm := new(BasicMap)
|
|
||||||
|
|
||||||
bm.tiles = tiles
|
|
||||||
bm.exploredTiles = make(map[engine.Position]Tile, 0)
|
|
||||||
bm.exploredStyle = exploredStyle
|
|
||||||
|
|
||||||
return bm
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bm *BasicMap) Tick(dt int64) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bm *BasicMap) Size() engine.Size {
|
|
||||||
return engine.SizeOf(len(bm.tiles[0]), len(bm.tiles))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bm *BasicMap) SetTileAt(x int, y int, t Tile) Tile {
|
|
||||||
if !bm.IsInBounds(x, y) {
|
|
||||||
return CreateStaticTile(x, y, TileTypeVoid())
|
|
||||||
}
|
|
||||||
|
|
||||||
bm.tiles[y][x] = t
|
|
||||||
|
|
||||||
return bm.tiles[y][x]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bm *BasicMap) TileAt(x int, y int) Tile {
|
|
||||||
if !bm.IsInBounds(x, y) {
|
|
||||||
return CreateStaticTile(x, y, TileTypeVoid())
|
|
||||||
}
|
|
||||||
|
|
||||||
tile := bm.tiles[y][x]
|
|
||||||
|
|
||||||
if tile == nil {
|
|
||||||
return CreateStaticTile(x, y, TileTypeVoid())
|
|
||||||
}
|
|
||||||
|
|
||||||
return tile
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bm *BasicMap) IsInBounds(x, y int) bool {
|
|
||||||
if x < 0 || y < 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if x >= bm.Size().Width() || y >= bm.Size().Height() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bm *BasicMap) ExploredTileAt(x, y int) Tile {
|
|
||||||
return bm.exploredTiles[engine.PositionAt(x, y)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bm *BasicMap) MarkExplored(x, y int) {
|
|
||||||
if !bm.IsInBounds(x, y) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tile := bm.TileAt(x, y)
|
|
||||||
|
|
||||||
bm.exploredTiles[engine.PositionAt(x, y)] = CreateStaticTileWithStyleOverride(tile.Position().X(), tile.Position().Y(), tile.Type(), bm.exploredStyle)
|
|
||||||
}
|
|
|
@ -1,127 +0,0 @@
|
||||||
package world
|
|
||||||
|
|
||||||
import "mvvasilev/last_light/engine"
|
|
||||||
|
|
||||||
type MultilevelMap struct {
|
|
||||||
layers []Map
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateMultilevelMap(maps ...Map) *MultilevelMap {
|
|
||||||
m := new(MultilevelMap)
|
|
||||||
|
|
||||||
m.layers = maps
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mm *MultilevelMap) Size() engine.Size {
|
|
||||||
if len(mm.layers) == 0 {
|
|
||||||
return engine.SizeOf(0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mm.layers[0].Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mm *MultilevelMap) SetTileAt(x, y int, t Tile) Tile {
|
|
||||||
return mm.layers[0].SetTileAt(x, y, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mm *MultilevelMap) UnsetTileAtHeight(x, y, height int) {
|
|
||||||
if len(mm.layers) < height {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mm.layers[height].SetTileAt(x, y, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mm *MultilevelMap) SetTileAtHeight(x, y, height int, t Tile) {
|
|
||||||
if len(mm.layers) < height {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
mm.layers[height].SetTileAt(x, y, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mm *MultilevelMap) CollectTilesAt(x, y int, filter func(t Tile) bool) []Tile {
|
|
||||||
tiles := make([]Tile, len(mm.layers))
|
|
||||||
|
|
||||||
if !mm.IsInBounds(x, y) {
|
|
||||||
return tiles
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := len(mm.layers) - 1; i >= 0; i-- {
|
|
||||||
tile := mm.layers[i].TileAt(x, y)
|
|
||||||
|
|
||||||
if tile != nil && !tile.Transparent() && filter(tile) {
|
|
||||||
tiles = append(tiles, tile)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return tiles
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mm *MultilevelMap) TileAt(x int, y int) Tile {
|
|
||||||
if !mm.IsInBounds(x, y) {
|
|
||||||
return CreateStaticTile(x, y, TileTypeVoid())
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := len(mm.layers) - 1; i >= 0; i-- {
|
|
||||||
tile := mm.layers[i].TileAt(x, y)
|
|
||||||
|
|
||||||
if tile != nil && !tile.Transparent() {
|
|
||||||
return tile
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateStaticTile(x, y, TileTypeVoid())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mm *MultilevelMap) IsInBounds(x, y int) bool {
|
|
||||||
if x < 0 || y < 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if x >= mm.Size().Width() || y >= mm.Size().Height() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mm *MultilevelMap) MarkExplored(x, y int) {
|
|
||||||
for _, m := range mm.layers {
|
|
||||||
m.MarkExplored(x, y)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mm *MultilevelMap) ExploredTileAt(x, y int) Tile {
|
|
||||||
for i := len(mm.layers) - 1; i >= 0; i-- {
|
|
||||||
tile := mm.layers[i].ExploredTileAt(x, y)
|
|
||||||
|
|
||||||
if tile != nil && !tile.Transparent() {
|
|
||||||
return tile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateStaticTile(x, y, TileTypeVoid())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mm *MultilevelMap) TileAtHeight(x, y, height int) Tile {
|
|
||||||
if !mm.IsInBounds(x, y) {
|
|
||||||
return CreateStaticTile(x, y, TileTypeVoid())
|
|
||||||
}
|
|
||||||
|
|
||||||
if height > len(mm.layers)-1 {
|
|
||||||
return CreateStaticTile(x, y, TileTypeVoid())
|
|
||||||
}
|
|
||||||
|
|
||||||
return mm.layers[height].TileAt(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mm *MultilevelMap) Tick(dt int64) {
|
|
||||||
for _, l := range mm.layers {
|
|
||||||
l.Tick(dt)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,274 +0,0 @@
|
||||||
package world
|
|
||||||
|
|
||||||
import (
|
|
||||||
"mvvasilev/last_light/engine"
|
|
||||||
"mvvasilev/last_light/game/item"
|
|
||||||
"mvvasilev/last_light/game/npc"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Material uint
|
|
||||||
|
|
||||||
const (
|
|
||||||
MaterialGround Material = iota
|
|
||||||
MaterialRock
|
|
||||||
MaterialWall
|
|
||||||
MaterialGrass
|
|
||||||
MaterialVoid
|
|
||||||
MaterialClosedDoor
|
|
||||||
MaterialOpenDoor
|
|
||||||
MaterialStaircaseDown
|
|
||||||
MaterialStaircaseUp
|
|
||||||
)
|
|
||||||
|
|
||||||
type TileType struct {
|
|
||||||
Material Material
|
|
||||||
Passable bool
|
|
||||||
Presentation rune
|
|
||||||
Transparent bool
|
|
||||||
Opaque bool
|
|
||||||
Style tcell.Style
|
|
||||||
}
|
|
||||||
|
|
||||||
func TileTypeGround() TileType {
|
|
||||||
return TileType{
|
|
||||||
Material: MaterialGround,
|
|
||||||
Passable: true,
|
|
||||||
Presentation: '.',
|
|
||||||
Transparent: false,
|
|
||||||
Opaque: false,
|
|
||||||
Style: tcell.StyleDefault,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TileTypeRock() TileType {
|
|
||||||
return TileType{
|
|
||||||
Material: MaterialRock,
|
|
||||||
Passable: false,
|
|
||||||
Presentation: '█',
|
|
||||||
Transparent: false,
|
|
||||||
Opaque: true,
|
|
||||||
Style: tcell.StyleDefault,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TileTypeGrass() TileType {
|
|
||||||
return TileType{
|
|
||||||
Material: MaterialGrass,
|
|
||||||
Passable: true,
|
|
||||||
Presentation: ',',
|
|
||||||
Transparent: false,
|
|
||||||
Opaque: false,
|
|
||||||
Style: tcell.StyleDefault,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TileTypeVoid() TileType {
|
|
||||||
return TileType{
|
|
||||||
Material: MaterialVoid,
|
|
||||||
Passable: false,
|
|
||||||
Presentation: ' ',
|
|
||||||
Transparent: true,
|
|
||||||
Opaque: true,
|
|
||||||
Style: tcell.StyleDefault,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TileTypeWall() TileType {
|
|
||||||
return TileType{
|
|
||||||
Material: MaterialWall,
|
|
||||||
Passable: false,
|
|
||||||
Presentation: '#',
|
|
||||||
Transparent: false,
|
|
||||||
Opaque: true,
|
|
||||||
Style: tcell.StyleDefault.Background(tcell.ColorGray),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TileTypeClosedDoor() TileType {
|
|
||||||
return TileType{
|
|
||||||
Material: MaterialClosedDoor,
|
|
||||||
Passable: false,
|
|
||||||
Transparent: false,
|
|
||||||
Presentation: '[',
|
|
||||||
Opaque: true,
|
|
||||||
Style: tcell.StyleDefault.Foreground(tcell.ColorLightSteelBlue).Background(tcell.ColorSaddleBrown),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TileTypeOpenDoor() TileType {
|
|
||||||
return TileType{
|
|
||||||
Material: MaterialClosedDoor,
|
|
||||||
Passable: false,
|
|
||||||
Transparent: false,
|
|
||||||
Presentation: '_',
|
|
||||||
Opaque: false,
|
|
||||||
Style: tcell.StyleDefault.Foreground(tcell.ColorLightSteelBlue),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TileTypeStaircaseDown() TileType {
|
|
||||||
return TileType{
|
|
||||||
Material: MaterialStaircaseDown,
|
|
||||||
Passable: true,
|
|
||||||
Transparent: false,
|
|
||||||
Presentation: '≡',
|
|
||||||
Opaque: false,
|
|
||||||
Style: tcell.StyleDefault.Foreground(tcell.ColorDarkSlateGray).Attributes(tcell.AttrBold),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TileTypeStaircaseUp() TileType {
|
|
||||||
return TileType{
|
|
||||||
Material: MaterialStaircaseUp,
|
|
||||||
Passable: true,
|
|
||||||
Transparent: false,
|
|
||||||
Presentation: '^',
|
|
||||||
Opaque: false,
|
|
||||||
Style: tcell.StyleDefault.Foreground(tcell.ColorDarkSlateGray).Attributes(tcell.AttrBold),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tile interface {
|
|
||||||
Position() engine.Position
|
|
||||||
Presentation() (rune, tcell.Style)
|
|
||||||
Passable() bool
|
|
||||||
Transparent() bool
|
|
||||||
Opaque() bool
|
|
||||||
Type() TileType
|
|
||||||
}
|
|
||||||
|
|
||||||
type StaticTile struct {
|
|
||||||
position engine.Position
|
|
||||||
t TileType
|
|
||||||
|
|
||||||
style tcell.Style
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateStaticTile(x, y int, t TileType) Tile {
|
|
||||||
st := new(StaticTile)
|
|
||||||
|
|
||||||
st.position = engine.PositionAt(x, y)
|
|
||||||
st.t = t
|
|
||||||
st.style = t.Style
|
|
||||||
|
|
||||||
return st
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateStaticTileWithStyleOverride(x, y int, t TileType, style tcell.Style) Tile {
|
|
||||||
return &StaticTile{
|
|
||||||
position: engine.PositionAt(x, y),
|
|
||||||
t: t,
|
|
||||||
style: style,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *StaticTile) Position() engine.Position {
|
|
||||||
return st.position
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *StaticTile) Presentation() (rune, tcell.Style) {
|
|
||||||
return st.t.Presentation, st.style
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *StaticTile) Passable() bool {
|
|
||||||
return st.t.Passable
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *StaticTile) Transparent() bool {
|
|
||||||
return st.t.Transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *StaticTile) Opaque() bool {
|
|
||||||
return st.t.Opaque
|
|
||||||
}
|
|
||||||
|
|
||||||
func (st *StaticTile) Type() TileType {
|
|
||||||
return st.t
|
|
||||||
}
|
|
||||||
|
|
||||||
type ItemTile struct {
|
|
||||||
position engine.Position
|
|
||||||
item item.Item
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateItemTile(position engine.Position, item item.Item) *ItemTile {
|
|
||||||
it := new(ItemTile)
|
|
||||||
|
|
||||||
it.position = position
|
|
||||||
it.item = item
|
|
||||||
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *ItemTile) Item() item.Item {
|
|
||||||
return it.item
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *ItemTile) Position() engine.Position {
|
|
||||||
return it.position
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *ItemTile) Presentation() (rune, tcell.Style) {
|
|
||||||
return it.item.Type().TileIcon(), it.item.Type().Style()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *ItemTile) Passable() bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *ItemTile) Transparent() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *ItemTile) Opaque() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (it *ItemTile) Type() TileType {
|
|
||||||
return TileType{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type EntityTile interface {
|
|
||||||
Entity() npc.MovableEntity
|
|
||||||
Tile
|
|
||||||
}
|
|
||||||
|
|
||||||
type BasicEntityTile struct {
|
|
||||||
entity npc.MovableEntity
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateBasicEntityTile(entity npc.MovableEntity) *BasicEntityTile {
|
|
||||||
return &BasicEntityTile{
|
|
||||||
entity: entity,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bet *BasicEntityTile) Entity() npc.MovableEntity {
|
|
||||||
return bet.entity
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bet *BasicEntityTile) Position() engine.Position {
|
|
||||||
return bet.entity.Position()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bet *BasicEntityTile) Presentation() (rune, tcell.Style) {
|
|
||||||
return bet.entity.Presentation()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bet *BasicEntityTile) Passable() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bet *BasicEntityTile) Transparent() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bet *BasicEntityTile) Opaque() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bet *BasicEntityTile) Type() TileType {
|
|
||||||
return TileType{}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue