some inventory improvements

This commit is contained in:
anton.gurov 2019-11-19 17:25:24 +03:00
parent 0a6c642dc2
commit 25aad1c2f5
6 changed files with 201 additions and 132 deletions

99
TODO
View File

@ -60,105 +60,6 @@ Combat:
skills determine speed, acceleration, and accuracy skills determine speed, acceleration, and accuracy
accuracy is diff between where you aimed and where you actually applied the impulse accuracy is diff between where you aimed and where you actually applied the impulse
- как устроена тушка:
- иерархически - есть корень, у которого есть ветки и листья - как в классическом дереве, Ветки - [конечности]. Для каждой указан [относительный размер].
- функциональные системы
- [скелет] : череп, хребет, кости - отвечает за движение и применения силы
[Полезные инструменты]:
челюсти и зубы, когти, хвосты, хватательные всякие пальцы и ладони, лапы, копыта, рога, жала
- [обмен жидкостью] - сосуды, артерии, сердце, селезенка(иммунитет втч) - доставка энергии до всех конечностей
именно по этой жидкости считается гидроудар
- [получение и накопление энергии (ЖРАТ)] - желудок, кишки, печень(сопротивляемость ядам втч), селезенка(да, 2 системы)
- [нервные импульсы] - мозги, нервная ткань - передача урпавляющих импульсов
[Полезные инструменты]:
- [зрение] - глаза (или аналоги) и нервы
- [слух] - уши (иил аналоги) и нервы
- [обоняние] - нос и нервы
- [осязание] - кожа (вибриссы, антенны) и нервы
- [полезные инструменты] - .
крепятся к скелету
дают эффекты вроде возможности видеть, нюхать, хватать, стоять и ходить, ползать, бросать, царапать, бить, кусать и колоть, одевать итп
В этом смысле экипировка тоже входит в организм через конечность с соответствующей функцией, и требует дополнительного
Для каждого узла дерева веток/конечностей есть список [слоев тканей] (для каждого указана толщина) и [органов], которые там располагаются.
(прим - сделать наследуемые шаблоны [конечностей] - для гуманоида, четвероногого итп и [слоев тканей] -
млекопитающее, земноводное, рептилия, головоногое и шаблон [органов])
Для каждой системы есть необходимый для функционирования список органов и реально располагаемый (два сердца anyone?)
Для каждого слоя в конечностях обозначается система, которой он принадлежит.
Для каждого органа в конечностях обозначается система, которой он принадлежит.
Пока органов, соединенных по всему дереву тканями больше, чем необходимо по минимальному списку - все в порядке. При потерях система выходит из строя:
- Нервная система - отключается _все_ действия ниже по дереву. Полная остановка/уничтожение - помираем.
- Обмен жидкостью - начинаем терять уровень внутреннего давления, как только он ниже критического - помираем
- Скелет - теряем возможности соответствующие поврежденной части отсюда и ниже по дереву. При обнажении CORE - помираем (разрубили пополам, блин!)
- Энергия (ЖРАТ) - при повреждении чувствуем себя плохо (деориентация), в перспективе - прекращение притока энергии и остановка обмена жидкостью и неврной деятельности, т.е. помираем
Кроме того, при потерях растет уровень боли. При превышении критического порога - помираем от болевого шока
(оторвали мишке лапы? помирает прямо тут, не ждем потери давления крови).
- как считать дамаг:
находим [кинетическую энергию] = масса на скорость движения. Для массы плотность, для скорости - характеристики тушки
находим цель и в ней часть куда всё это попадет (применяется скилл меткости для нахождения разницы между заявленным и получившимся)
Это не только смещение по частям, но и максимальная глубина проникновения в слоях (исходя из скиллов, удади и профиля оружия)
(тут нужно подумать о кинематике движения, хотя можно обойтись рандомом)
находим площадь, на котрую придется кинетическая энергия (профиль оружия - колющий, режущий, давящий)
[импульс] = [остаток импульса] = [кинетическая энергия] / [площадь]
находим слои, покрывающие эту [площадь] (от наружных к внутренним до core)
Инициализируем [счетчик всего времени соударения]
По каждому слою внутрь:
Делим [остаток импульса] на [время соударения] (определяется _упругостью_ материала СЛОЯ)
Добавляем [время соударения] к [счетчику всего времени соударения] (нужен для расчета гидроудара позже)
Делим [остаток импульса] на [площадь поперечного сечения ударной части]
(скажем для давящего - результат 5/2 , для режущего - 1, для колющего - 1/4)
получаем характеристику удара: [давление] силу поделенную на площадь (скажем в ньютонах / м2)
Сравниваем с хрупкостью материала слоя: если [давление] * [счетчик всего времени соударения] > [предела хрупкости]
то провалено - разрушается ВЕСЬ СЛОЙ в пределах части -> переходим к следующему слою, не уменьшая [остаток импульса]
Сравниваем [остаток импульса] с [ударной вязкостью материала]:
Так как ударная вязкость в джоулях, а импульс в ньютонах - умножаем остаток импульса на [толщину слоя] / [площадь],
это произведение вычитаем из [остатка импульса]
Если от импульса еще что-то осталось -> переходим к следующему слою, иначе выходим
Если слои кончились, а от импульса еще что-то осталось:
Считаем off-balance / сбитие с ног / отправление в полет - для дубин это будет часто =)
Пороговые значния придется подбирать.
foreach [список поврежденных слоев]:
смотрим какие штуки находились в этой части в этом слое, роллим [поцарапан / проткнут / ушиблен / уничтожен].
- Для экипировки - применяем повреждения (ухудшаем зарактеристики)
- Для органов - добавляем к кровотечению кровооборот органа, добавляем к уровню нервной нагрузки "важность" органа
- При разрушении связок / нервов / мыщц / костей - отключаем всё что их требует ниже по дереву тушки
Расчет гидроудара (специально для дубин и проч):
есть [площадь], [время], [импульс]
но считать колебания и скорость звука в жидкой среде (а это километры в секунды) считать дорого, просто передаем
топ-3 внутренним [слоям] и [органам], чья [жесткость? слоя] и [относительный размер|органа] больше всех остальных
тут присутствующих 30% от [импульса] умноженного на [площадь] / [счетчик всего времени соударения]. Таким образом,
надетые доспехи и поддоспешник оставоляют вероятность сломать кость, а вот три слоя брони - уже нет.
Но поскольку такой симулятор скотобойни за одно и то же тело быстро приестся - играем за иллитида/мозгового слизня, который
берет разые тушки под контроль, выбрасывает по мере использования и берет новые.
Соответственно направления развития
- Зов - приманивает тушки получше
- Контроль - позволяет более полно использовать инзначальные возможности тушки, или даже применять новые
- Мутации - отращивание и улучшение тушки, замена материалов, дополнительные конечности/органы, смена шаблона тканей, переход на другой [метаболизм] итп.
- Внедрение и изъятие - запоминание умений тушки и внедрение в другие - от пользования дубиной до телепортационных заклинаний
За опыт открываются [слоты] для этих возможностей. Макисмальное количество ограничено (скажем 7)
За применения [умений тушки] можно получить ее умения в [слот] (направление контроль).
Любое [умение] занимает какой-то % от слота, может быть больше размера слота. [Объединить слоты] в один больший (но
меньший по размеру чем сумма начальных двух) можно только за [эпические свершения] - сдачу квеста или победу над OOD.
За жти же [эпические очки] открываются новые [умения] в ветках (ограничить деревом скиллов?).
При [гибели тушки] теряются рандомные [слоты] вместе с [умениями] что в них были (не складывайте яйца в одну корзину!).
Если заметят и уничтожат собственно иллитида - game over, high score.
- no hitpoints! blood is the life source - no hitpoints! blood is the life source
``` ```

View File

@ -133,7 +133,7 @@ func main() {
}).MakeList(), }).MakeList(),
) )
screenMgr.AddScreen("inventory", screens.InventoryScreen{ inv := screens.InventoryScreen{
MenuScreen: screens.NewMenuScreen( MenuScreen: screens.NewMenuScreen(
mw, mw,
screenMgr, screenMgr,
@ -144,10 +144,8 @@ func main() {
types.NewCenteredRect(mw.Rect, 70, 25), types.NewCenteredRect(mw.Rect, 70, 25),
true, ). true, ).
SetBgColor("#ef305c70"). SetBgColor("#ef305c70").
SetFgColor("white"). SetFgColor("white"),
SetItems([]interface{}{}).MakeList() }
},
)
screenMgr.AddScreen("devmenu", screens.NewDevmenuScreen( screenMgr.AddScreen("devmenu", screens.NewDevmenuScreen(
mw, mw,
@ -188,6 +186,7 @@ func main() {
controller.AddComponent(potion, items.Carried{Mass:5, Bulk:3}) controller.AddComponent(potion, items.Carried{Mass:5, Bulk:3})
controller.AddComponent(potion, items.Usable{}) controller.AddComponent(potion, items.Usable{})
controller.AddComponent(potion, items.Consumable{}) controller.AddComponent(potion, items.Consumable{})
controller.AddComponent(potion, ecs.Named{Name:"first potion"})
potion2 := controller.CreateEntity([]ecs.Component{}) potion2 := controller.CreateEntity([]ecs.Component{})
controller.AddComponent(potion2, types.Appearance{ controller.AddComponent(potion2, types.Appearance{
@ -200,11 +199,15 @@ func main() {
controller.AddComponent(potion2, items.Carried{Mass:5, Bulk:3}) controller.AddComponent(potion2, items.Carried{Mass:5, Bulk:3})
controller.AddComponent(potion2, items.Usable{}) controller.AddComponent(potion2, items.Usable{})
controller.AddComponent(potion2, items.Consumable{}) controller.AddComponent(potion2, items.Consumable{})
controller.AddComponent(potion2, ecs.Named{Name:"second potion"})
//fixme end setting up items //fixme end setting up items
State.Player = player State.Player = player
State.Controller = controller State.Controller = controller
screenMgr.AddScreen("inventory", inv.MakeInverntory(player))
//but every call to bearlibterminal must be wrapped to closure and passed to mainfunc //but every call to bearlibterminal must be wrapped to closure and passed to mainfunc
var exit = false var exit = false
for !exit { for !exit {

98
coremech.txt Normal file
View File

@ -0,0 +1,98 @@
- как устроена тушка:
- иерархически - есть корень, у которого есть ветки и листья - как в классическом дереве, Ветки - [конечности]. Для каждой указан [относительный размер].
- функциональные системы
- [скелет] : череп, хребет, кости - отвечает за движение и применения силы
[Полезные инструменты]:
челюсти и зубы, когти, хвосты, хватательные всякие пальцы и ладони, лапы, копыта, рога, жала
- [обмен жидкостью] - сосуды, артерии, сердце, селезенка(иммунитет втч) - доставка энергии до всех конечностей
именно по этой жидкости считается гидроудар
- [получение и накопление энергии (ЖРАТ)] - желудок, кишки, печень(сопротивляемость ядам втч), селезенка(да, 2 системы)
- [нервные импульсы] - мозги, нервная ткань - передача урпавляющих импульсов
[Полезные инструменты]:
- [зрение] - глаза (или аналоги) и нервы
- [слух] - уши (иил аналоги) и нервы
- [обоняние] - нос и нервы
- [осязание] - кожа (вибриссы, антенны) и нервы
- [полезные инструменты] - .
крепятся к скелету
дают эффекты вроде возможности видеть, нюхать, хватать, стоять и ходить, ползать, бросать, царапать, бить, кусать и колоть, одевать итп
В этом смысле экипировка тоже входит в организм через конечность с соответствующей функцией, и требует дополнительного
Для каждого узла дерева веток/конечностей есть список [слоев тканей] (для каждого указана толщина) и [органов], которые там располагаются.
(прим - сделать наследуемые шаблоны [конечностей] - для гуманоида, четвероногого итп и [слоев тканей] -
млекопитающее, земноводное, рептилия, головоногое и шаблон [органов])
Для каждой системы есть необходимый для функционирования список органов и реально располагаемый (два сердца anyone?)
Для каждого слоя в конечностях обозначается система, которой он принадлежит.
Для каждого органа в конечностях обозначается система, которой он принадлежит.
Пока органов, соединенных по всему дереву тканями больше, чем необходимо по минимальному списку - все в порядке. При потерях система выходит из строя:
- Нервная система - отключается _все_ действия ниже по дереву. Полная остановка/уничтожение - помираем.
- Обмен жидкостью - начинаем терять уровень внутреннего давления, как только он ниже критического - помираем
- Скелет - теряем возможности соответствующие поврежденной части отсюда и ниже по дереву. При обнажении CORE - помираем (разрубили пополам, блин!)
- Энергия (ЖРАТ) - при повреждении чувствуем себя плохо (деориентация), в перспективе - прекращение притока энергии и остановка обмена жидкостью и неврной деятельности, т.е. помираем
Кроме того, при потерях растет уровень боли. При превышении критического порога - помираем от болевого шока
(оторвали мишке лапы? помирает прямо тут, не ждем потери давления крови).
- как считать дамаг:
находим [кинетическую энергию] = масса на скорость движения. Для массы плотность, для скорости - характеристики тушки
находим цель и в ней часть куда всё это попадет (применяется скилл меткости для нахождения разницы между заявленным и получившимся)
Это не только смещение по частям, но и максимальная глубина проникновения в слоях (исходя из скиллов, удади и профиля оружия)
(тут нужно подумать о кинематике движения, хотя можно обойтись рандомом)
находим площадь, на котрую придется кинетическая энергия (профиль оружия - колющий, режущий, давящий)
[импульс] = [остаток импульса] = [кинетическая энергия] / [площадь]
находим слои, покрывающие эту [площадь] (от наружных к внутренним до core)
Инициализируем [счетчик всего времени соударения]
По каждому слою внутрь:
Делим [остаток импульса] на [время соударения] (определяется _упругостью_ материала СЛОЯ)
Добавляем [время соударения] к [счетчику всего времени соударения] (нужен для расчета гидроудара позже)
Делим [остаток импульса] на [площадь поперечного сечения ударной части]
(скажем для давящего - результат 5/2 , для режущего - 1, для колющего - 1/4)
получаем характеристику удара: [давление] силу поделенную на площадь (скажем в ньютонах / м2)
Сравниваем с хрупкостью материала слоя: если [давление] * [счетчик всего времени соударения] > [предела хрупкости]
то провалено - разрушается ВЕСЬ СЛОЙ в пределах части -> переходим к следующему слою, не уменьшая [остаток импульса]
Сравниваем [остаток импульса] с [ударной вязкостью материала]:
Так как ударная вязкость в джоулях, а импульс в ньютонах - умножаем остаток импульса на [толщину слоя] / [площадь],
это произведение вычитаем из [остатка импульса]
Если от импульса еще что-то осталось -> переходим к следующему слою, иначе выходим
Если слои кончились, а от импульса еще что-то осталось:
Считаем off-balance / сбитие с ног / отправление в полет - для дубин это будет часто =)
Пороговые значния придется подбирать.
foreach [список поврежденных слоев]:
смотрим какие штуки находились в этой части в этом слое, роллим [поцарапан / проткнут / ушиблен / уничтожен].
- Для экипировки - применяем повреждения (ухудшаем зарактеристики)
- Для органов - добавляем к кровотечению кровооборот органа, добавляем к уровню нервной нагрузки "важность" органа
- При разрушении связок / нервов / мыщц / костей - отключаем всё что их требует ниже по дереву тушки
Расчет гидроудара (специально для дубин и проч):
есть [площадь], [время], [импульс]
но считать колебания и скорость звука в жидкой среде (а это километры в секунды) считать дорого, просто передаем
топ-3 внутренним [слоям] и [органам], чья [жесткость? слоя] и [относительный размер|органа] больше всех остальных
тут присутствующих 30% от [импульса] умноженного на [площадь] / [счетчик всего времени соударения]. Таким образом,
надетые доспехи и поддоспешник оставоляют вероятность сломать кость, а вот три слоя брони - уже нет.
Но поскольку такой симулятор скотобойни за одно и то же тело быстро приестся - играем за иллитида/мозгового слизня, который
берет разые тушки под контроль, выбрасывает по мере использования и берет новые.
Соответственно направления развития
- Зов - приманивает тушки получше
- Контроль - позволяет более полно использовать инзначальные возможности тушки, или даже применять новые
- Мутации - отращивание и улучшение тушки, замена материалов, дополнительные конечности/органы, смена шаблона тканей, переход на другой [метаболизм] итп.
- Внедрение и изъятие - запоминание умений тушки и внедрение в другие - от пользования дубиной до телепортационных заклинаний
За опыт открываются [слоты] для этих возможностей. Макисмальное количество ограничено (скажем 7)
За применения [умений тушки] можно получить ее умения в [слот] (направление контроль).
Любое [умение] занимает какой-то % от слота, может быть больше размера слота. [Объединить слоты] в один больший (но
меньший по размеру чем сумма начальных двух) можно только за [эпические свершения] - сдачу квеста или победу над OOD.
За жти же [эпические очки] открываются новые [умения] в ветках (ограничить деревом скиллов?).
При [гибели тушки] теряются рандомные [слоты] вместе с [умениями] что в них были (не складывайте яйца в одну корзину!).
Если заметят и уничтожат собственно иллитида - game over, high score.

View File

@ -2,6 +2,11 @@ package items
import "lab.zaar.be/thefish/alchemyst-go/engine/ecs" import "lab.zaar.be/thefish/alchemyst-go/engine/ecs"
type Named interface {
GetName() string
}
var Controller *ecs.Controller var Controller *ecs.Controller
func Init(ctrl *ecs.Controller) { func Init(ctrl *ecs.Controller) {

View File

@ -1,32 +1,79 @@
package screens package screens
import ( import (
"fmt"
"lab.zaar.be/thefish/alchemyst-go/engine/ecs" "lab.zaar.be/thefish/alchemyst-go/engine/ecs"
"lab.zaar.be/thefish/alchemyst-go/engine/items"
"lab.zaar.be/thefish/alchemyst-go/engine/types" "lab.zaar.be/thefish/alchemyst-go/engine/types"
blt "lab.zaar.be/thefish/bearlibterminal"
"strings" "strings"
) )
type InventoryScreen struct { type InventoryScreen struct {
*MenuScreen *MenuScreen
items []ecs.Entity who ecs.Entity
cursor int cursor int
offset int offset int
pageSize int pageSize int
selected []int
prepared preparedList
} }
func (is *InventoryScreen) MakeInverntory() *InventoryScreen { type displayItem struct {
Entity ecs.Entity
Name string
Index string
Selected bool
}
type preparedList []displayItem
func (p *preparedList) Prepare(is *InventoryScreen) {
//fixme add item class sorting
//fixme text instead of list to rpint and use printinside()
bp := items.Controller.GetComponent(is.who, ecs.BackpackComponent).(items.Backpack)
is.prepared = make([]displayItem, len(bp.GetItems()))
//todo prepare item list
for i, ent := range bp.GetItems() {
namec := items.Controller.GetComponent(ent, ecs.Named{}.Type()).(ecs.Named)
is.prepared[i] = displayItem{
Entity: ent,
Name: namec.Name,
Index: string(runeIndex[i]),
}
}
}
func (is *InventoryScreen) MakeInverntory(who ecs.Entity) *InventoryScreen {
is.drawFunc = is.InventoryRender is.drawFunc = is.InventoryRender
is.inputFunc = is.HandleInput is.inputFunc = is.HandleInput
is.who = who
return is return is
} }
func (is *InventoryScreen) SetItems(items []ecs.Entity) *InventoryScreen { func (is *InventoryScreen) Enter() {
is.items = items is.redraw = true
return is is.offset = 0
is.cursor = 0
is.prepared = preparedList{}
is.prepared.Prepare(is)
} }
func (is *InventoryScreen) HandleInput(input string) { func (is *InventoryScreen) HandleInput(input string) {
if strings.Contains(string(runeIndex), strings.Replace(input, "Shift+", "", -1)) {
if strings.Contains("Shift+", input) {
input = strings.ToUpper(input)
}
for i, _ := range is.prepared {
if is.prepared[i].Index == input {
is.prepared[i].Selected = !is.prepared[i].Selected
}
}
return
}
switch input { switch input {
case "PageUp": case "PageUp":
is.offset = is.offset - 1 is.offset = is.offset - 1
@ -36,16 +83,13 @@ func (is *InventoryScreen) HandleInput(input string) {
break break
case "PageDown": case "PageDown":
is.offset = is.offset + 1 is.offset = is.offset + 1
if is.offset > len(is.items)-1 { if is.offset > len(is.prepared)-1 {
is.offset = len(is.items) - 1 is.offset = len(is.prepared) - 1
} }
break break
case "enter": case "enter":
//select current under cursor //select current under cursor
break; break;
case "a","b","c","d","e","f","g","h","i","j","k","l":
//selct by letter
break;
case "Escape": case "Escape":
fallthrough fallthrough
case "Space": case "Space":
@ -54,8 +98,6 @@ func (is *InventoryScreen) HandleInput(input string) {
} }
} }
func (is *InventoryScreen) InventoryRender() { func (is *InventoryScreen) InventoryRender() {
menuLayer := is.mw.GetLayer("menu") menuLayer := is.mw.GetLayer("menu")
menuLayer.ClearRect(is.Rect) menuLayer.ClearRect(is.Rect)
@ -72,26 +114,35 @@ func (is *InventoryScreen) InventoryRender() {
_, headerHeight := menuLayer.PrintInside(is.Rect, is.header, blt.TK_ALIGN_LEFT) _, headerHeight := menuLayer.PrintInside(is.Rect, is.header, blt.TK_ALIGN_LEFT)
itemField := types.Rect{is.X, is.Y + headerHeight + 1, is.W, is.H - headerHeight - footerHeight} itemField := types.Rect{is.X, is.Y + headerHeight + 1, is.W, is.H - headerHeight - footerHeight}
_ = itemField _ = itemField
var ilw, ilh int
if (len(is.items) > 0) { if (len(is.prepared) > 0) {
//fixme itemfield object, scroller, inputhandler, current selected item
menuItems := make([]string, 0) for i := is.offset; i < itemField.H; i++ {
for i := is.offset; i < len(is.items); i++ { if i < len(is.prepared) {
if string(is.items[i].(string)) != "" { selectedColor := "blue"
menuItems = append(menuItems, is.items[i].(string)) selectedDash := "-"
if is.prepared[i].Selected {
selectedColor = "green"
selectedDash = "+"
}
if is.cursor == i {
menuLayer.WithColor("yellow")
} else {
menuLayer.WithColor("white")
}
menuStr := fmt.Sprintf("[color=%s]%s[/color] %s %s", selectedColor, is.prepared[i].Index, selectedDash, is.prepared[i].Name)
if len(menuStr) > itemField.W - 2 {
menuStr = menuStr[0:itemField.W - 2]
}
menuLayer.PutStringInto(itemField,i,menuStr,1)
} }
} }
ilw, ilh = menuLayer.PrintInside(itemField, strings.Join(menuItems, "\n"), blt.TK_ALIGN_LEFT)
} }
if ilh < len(is.items) { if is.cursor < len(is.prepared) {
is.drawScrollBar(menuLayer, itemField) is.drawScrollBar(menuLayer, itemField)
} }
if ilw > itemField.W-4 {
fmt.Printf("Excess width of item names found! Need h-scroll of certain names")
}
} }
//func (ms *InventoryScreen) UseEcs() bool {return true} //func (ms *InventoryScreen) UseEcs() bool {return true}
//func (ms *InventoryScreen) Enter() {} //func (ms *InventoryScreen) Enter() {}

View File

@ -97,6 +97,17 @@ func (layer Layer) PrintInside(rect types.Rect, text string, alignment int) (wid
return blt.PrintExt(rect.X + 2, rect.Y + 2, rect.W - 4, rect.H - 4, alignment, text) return blt.PrintExt(rect.X + 2, rect.Y + 2, rect.W - 4, rect.H - 4, alignment, text)
} }
func (layer Layer) PutStringInto(rect types.Rect, topOffset int, string string, alignment int) bool {
if len(string) > rect.W - 2 {
string = string[:rect.W-5]+"..."
}
if topOffset > rect.H - 2 {
return false
}
blt.PrintExt(rect.X + 2, rect.Y + topOffset, rect.W - 4, rect.H - 4, alignment, string)
return true
}
func (layer *Layer) Decorate(f func(args ...interface{})) func(args ...interface{}) { func (layer *Layer) Decorate(f func(args ...interface{})) func(args ...interface{}) {
return func(args ...interface{}) { return func(args ...interface{}) {
layer.before() layer.before()