Compare commits
18 Commits
20ba03c758
...
master
Author | SHA1 | Date | |
---|---|---|---|
2be7717477 | |||
59ddec5a37 | |||
4913b0a567 | |||
dc2e6ea2b5 | |||
5613744d6e | |||
4c0117b6a7 | |||
4f18b6db18 | |||
f52e235799 | |||
def43265de | |||
09bfa44c78 | |||
31d5be5d07 | |||
3886a05ce2 | |||
d9a8529a20 | |||
76780d7936 | |||
9f3eaafa3f | |||
d2b22f4760 | |||
2352ec4f0a | |||
e738568c14 |
178
10-rooms-dungeon-zone-gen-rules.txt
Normal file
178
10-rooms-dungeon-zone-gen-rules.txt
Normal file
@ -0,0 +1,178 @@
|
||||
|
||||
Десятикомнатное подземелье
|
||||
Арсенал
|
||||
Это перевод метода генерации небольших подземелий и детальное описание этого метода, сведённое в одну статью. Также я посчитал, что было бы неплохо добавить какой-то пример работы генератора и выбрал для этого короткое приключение, которое было создано при помощи этого генератора и полностью оформлено для использования. И конечно же буклет с генератором для любителей коллекционировать PDF и делать распечатки.
|
||||
|
||||
Как сделать «десятикомнатное» или «30-минутное» подземелье
|
||||
|
||||
Оригинал: priestessofspiders.blogspot.com/2022/05/how-to-make-ten-room30-minute-dungeon.html
|
||||
|
||||
Вернёмся назад в 2018 год. Когда я начал свой блог, главная вещь, которую я сделал, было «30-минутное подземелье». Я сделал несколько постов об этом, в паре из которых были уроки, как сделать всё самостоятельно. Целью было создать относительно готовое для игры приключение без карты и характеристик всего за 30 минут.
|
||||
|
||||
Позже я несколько отошёл от аспекта «30 минут» и обычно сейчас просто создаю подземелье за 10 минут на одном листе блокнота и дорабатываю его во что-то приличное на досуге.
|
||||
|
||||
В этом посте я хочу собрать всю информацию из моих предыдущих постов про дизайн подземелий в один большой для удобства. Также для удобства я буду ссылаться на этот концепт как на «десятикомнатное подземелье» в остальной статье.
|
||||
|
||||
Введение
|
||||
|
||||
В среднем десятикомнатное подземелье должно содержать следующее:
|
||||
— Три встречи с монстрами, возможно битвами;
|
||||
— Две ловушки или угрозы;
|
||||
— Одна странная штуковина, с которой игроки могут взаимодействовать и экспериментировать;
|
||||
— Один персонаж ведущего, который не является сразу же враждебным для партии;
|
||||
— Три пустые зоны, свободные от всего вышеперечисленного.
|
||||
|
||||
Я нахожу именно такой баланс подходящим для интересного полутора- или двухчасового приключения при использовании быстрой системы, но конечно же это зависит от вашего стиля игры.
|
||||
|
||||
Это только рекомендации, не каждое десятикомнатное подземелье будет выглядеть именно так. Для примера, забытый лабиринт изготовителя ловушек может содержать четыре комнаты с ловушками, две странные штуки, одного персонажа ведущего, одну безопасную зону и две встречи с монстрами.
|
||||
|
||||
Что касается сокровищ, эмпирическое правило таково, что они должны быть в четырёх различных зонах, и обычно среди сокровищ находится один магический предмет любого типа. Опять же, это только рекомендация, так что можно поступать как угодно. Например, для забытого лабиринта изготовителя ловушек имеет смысл разместить только одно сокровище в самом конце, после всех ловушек, которые его защищают.
|
||||
|
||||
Описание зон
|
||||
|
||||
Когда-то я написал статью с таблицами под d6, с помощью которых можно было генерировать особенные описания для ловушек, пустых комнат, персонажей ведущего, особых зон и боевых столкновений. Привожу здесь их полную копию.
|
||||
|
||||
Пустая комната
|
||||
1 Что-то, указывающее на персонажа ведущего
|
||||
2 Что-то, рассказывающее историю подземелья
|
||||
3 Что-то, предупреждающее о столкновении с монстрами и возможной битве
|
||||
4 Что-то, что предупреждает об особой зоне
|
||||
5 Что-то, предупреждающее о ловушке
|
||||
6 Что-то полезное для персонажей
|
||||
|
||||
Особые зоны
|
||||
1 Что-то, что меняет персонажа, который с ним взаимодействует
|
||||
2 Что-то физически невозможное
|
||||
3 Что-то, что даёт дары в обмен на подношения
|
||||
4 Что-то, что может обернуться как великой наградой, так и великой катастрофой
|
||||
5 Что-то, что выглядит вполне обычно, но на самом деле необычное
|
||||
6 Здесь можно почувствовать или увидеть что-то странное и непонятное
|
||||
|
||||
Персонажи ведущего
|
||||
1 Союзник
|
||||
2 Злодей
|
||||
3 Жертва
|
||||
4 Конкурирующий исследователь
|
||||
5 Квестодатель
|
||||
6 Странная или чудаковатая личность
|
||||
|
||||
Боевые встречи
|
||||
1 Орда слабых противников
|
||||
2 Один сильный противник
|
||||
3 Слабый противник и его охрана
|
||||
4 Пара сильных противников
|
||||
5 Сильный противник и его подчинённые
|
||||
6 Группа компетентных, достойных противников
|
||||
|
||||
Ловушки
|
||||
1 Что-то, что причиняет неудобство
|
||||
2 Что-то, что убивает
|
||||
3 Что-то, что делает жертву недееспособной
|
||||
4 Что-то, что ловит жертву
|
||||
5 Что-то, что предупреждает или призывает врагов
|
||||
6 Что-то, что разделяет персонажей
|
||||
|
||||
Расширенный концепт «30-минутного» подземелья
|
||||
|
||||
Оригинал: priestessofspiders.blogspot.com/2019/04/30-minute-dungeon-expanded-concept.html
|
||||
|
||||
Основываясь на своём опыте ведущего, я нахожу лучшим соотношением комнат в подземелье следующее:
|
||||
|
||||
30% боевых столкновений
|
||||
30% пустых
|
||||
20% ловушек
|
||||
10% персонажей ведущего
|
||||
10% странных вещей
|
||||
|
||||
Что на практике, когда дело доходит до создания подземелья за 30 минут — три боевых столкновения, три пустые комнаты, две ловушки, персонаж ведущего и что-то странное, с чем можно повозиться. А также один магический предмет, брошенный где-то рядом.
|
||||
|
||||
Это всё здорово, но я понял, что сам не полностью понимаю, что именно делает хорошей любую типовую комнату подземелья. Что отделяет хорошую ловушку от плохой? Когда боевое столкновение выглядит как скучное перекидывание кубиками, а когда полно тактики и элегантности? Как мне написать интересных персонажей?
|
||||
|
||||
Вот что я могу ответить на подобные вопросы:
|
||||
|
||||
Что нужно для хорошего боевого столкновения?
|
||||
|
||||
Причина для группы, чтобы сражаться. Враги должны блокировать место, куда им нужно попасть, там могут быть охраняемые сокровища, или может быть невинный человек для спасения.
|
||||
|
||||
Окружение должно быть интересным и пригодным к использованию. Должно быть что-то и для врагов и для группы, что они могут использовать, чтобы получить преимущество. Столы можно перевернуть и использовать для укрытия; светильники, которые можно сбить с потолка, взрывающиеся бочки и т.п. Это побуждает игроков мыслить тактически и делает бои по-настоящему интересными.
|
||||
|
||||
Враги должны мыслить тактически и действовать осмысленно. Есть очевидные исключение из этого правила, безмозглые зомби скорее всего будут глупее бандитов, но общий принцип понятен. Враги должны бежать, если изранены, искать укрытие от стрелкового оружия, координироваться друг с другом и логично выбирать цели.
|
||||
|
||||
Мне нравится один скриншот из мода Medevil для Wolfenstein 3D, и я считаю это составляет основу интересных столкновений. Стол в центре, который служит укрытием, два разных врага с уникальными тактикой и стратегией. Если это была бы D&D, игроки могли бы использовать гобелены со стены для получения преимущества, например, накинув один на врага, чтобы ослепить его!
|
||||
|
||||
Что нужно для хорошей ловушки?
|
||||
|
||||
Причину для игроков, чтобы они захотели обойти её. Если это просто боковая комната без сокровищ и (в ней) сундук с отравленным замком, это не хорошая ловушка, это просто мудачество. Правильная ловушка должна награждать каким-то образом партию за её преодоление, через сокровища, доступ к остальной части подземелья и т.п.
|
||||
|
||||
Способ обойти или деактивировать её. Конечно, логически, строители подземелья хотели бы ловушку, которую нельзя обойти или отключить, но мы играем не в игру по убийству персонажей игроков, мы играем в игру про фантастические приключения! Разместите хитрый скрытый рычаг, чтобы отключить дробилку или магическое слово, которое заставляет шипы в стене втянуться. Или просто добавьте обходной путь, позволяющий избежать того и другого! Смертельная ловушка не должна быть смертельной ловушкой, если так понятнее.
|
||||
|
||||
Способ заметить ловушку до того, как она сделает своё дело. Не нужно немедленно говорить игрокам что-нибудь при входе в комнату, но если они начинают смотреть по сторонам и задавать вопросы, отвечайте им! Если они присматриваются, пусть заметят растяжку или нажимную пластину. Поощряйте исследование и наблюдение.
|
||||
|
||||
Мне нравится иллюстрация из D&D 4-й редакции. Хотя на мой вкус это чересчур, но на изображении приключенцы бегут от огромного камня, пока по ним стреляют стрелы, это круто.
|
||||
|
||||
Что нужно для хорошей пустой комнаты?
|
||||
|
||||
Пустая комната должна иметь причину для существования. Это могут быть двери или коридоры, которые ведут в другие комнаты, немного сокровищ или какие-то предания и истории, стоящие за ней.
|
||||
|
||||
В «пустой» комнате должно быть что-то, что относится к тематике подземелья или истории. Например, лабиринт волшебника может иметь портрет волшебника, или маленькую лабораторию в ужасном состоянии.
|
||||
|
||||
Что нужно для хорошей особой комнаты?
|
||||
|
||||
По правде, делайте с ней что угодно, это ваш шанс разместить в подземелье что-нибудь дурацкое и постороннее. Просто подумайте о чём-то странном, для чего нет особо осмысленной причины здесь находиться и вставьте это сюда.
|
||||
|
||||
К чёрту баланс, это место не следует правилам. Это не значит, что оно очень смертельное или ломает игру, это может быть просто странный эффект или встреча, вроде огра, которого на самом деле не существует или что-то такое.
|
||||
|
||||
Что нужно для хорошего персонажа ведущего?
|
||||
|
||||
Манерность или особый голос. Дай этому парню дурацкий голосок, давай, это поможет игрокам запомнить его.
|
||||
|
||||
Также важны цели и мотивация. Почему этот человек в подземелье? Что он хочет?
|
||||
|
||||
Персонаж ведущего нуждается в симпатиях и антипатиях. Персонаж, который смертельно боится крыс и испытывает странную тягу к волшебникам намного интереснее того, у кого нет никаких предпочтений.
|
||||
|
||||
Мастерская Мастера Големов
|
||||
|
||||
Я позволил себе добавить заметки к тексту, проясняющие где именно находятся те или иные элементы, поскольку в оригинале содержатся просто перечисление того, что выдал генератор. Описания комнат смотрите в приложенном PDF. Исходные характеристики врагов писались под Lamentation of the Flame Princess, но позже автор изменил их так, чтобы они условно подходили под любую OSR систему.
|
||||
|
||||
Оригинал: eldritchfields.blogspot.com/2019/12/dungeon-30-minute-dungeon-golem-masters.html
|
||||
|
||||
Мастера Големов, создателя дорогих искусственных слуг, не видели уже некоторое время. Его дом стоит тёмен и тих. Осмелитесь ли вы войти?
|
||||
|
||||
«30-минутное подземелье» — отличное упражнение. Это новое подземелье я написал примерно за 35 минут, затем потратил немного времени на то, чтобы перерисовать карту и отформатировать контент в одностраничный PDF. Наслаждайтесь!
|
||||
|
||||
Рабочие заметки
|
||||
|
||||
30-минутное подземелье, включающее:
|
||||
— 3 пустые комнаты
|
||||
— 3 боевых столкновения
|
||||
— 2 ловушки
|
||||
— 1 персонаж ведущего
|
||||
— 1 особая странная штуковина
|
||||
|
||||
Три пустые комнаты:
|
||||
— Что-то, рассказывающее историю подземелья (бухгалтерская книга в комнате 1)
|
||||
— Что-то полезное для персонажей (свиток в комнате 6)
|
||||
— Что-то, указывающее на персонажа ведущего (следы в комнате 2)
|
||||
|
||||
Особая комната:
|
||||
— Что-то, что может обернуться как великой наградой, так и великой катастрофой (комната 10, где с вероятностью 50% персонажи получат или сильного слугу, или жестокую бойню)
|
||||
|
||||
Персонаж ведущего:
|
||||
— Жертва (изувеченный голем в комнате 4)
|
||||
|
||||
Три боевых столкновения
|
||||
— Орда слабых противников (анимированные глиняные руки в комнате 3)
|
||||
— Слабый противник и его охрана (Мастер Големов и его создания в комнате 8)
|
||||
— Сильный противник и его подчинённые (голем Боакс и гаргульи в комнате 5)
|
||||
|
||||
Ловушка:
|
||||
— Что-то, что убивает (отравленная игла на входе в комнату 9)
|
||||
— Что-то, что ловит жертву (глиняные ямы под старыми половицами в комнате 7)
|
||||
|
||||
В оригинале статьи нет статистики по сокровищам, но я всё-таки подсчитал и привожу тут, чтобы было понимание, как их предлагается раскладывать. Мы имеем:
|
||||
— Сокровище в комнате 3, охраняемое монстрами
|
||||
— Магический предмет в пустой комнате 6
|
||||
— Сокровище в комнате 9 с ловушкой (хотя возможно оно считается за два из-за разного типа ценностей)
|
||||
Итого 3(4) сокровища и одно из них — магический предмет.
|
||||
|
||||
В каком-то смысле сокровищами будут и ритуальные предметы в комнате 10. Хоть книга наверняка не является самоучителем «Големансия за 21 день» (истинное искусство содержится в голове Мастера Големов, а книга — лишь его заметки), да и ритуальные принадлежности наверняка самые обычные, но всё-таки за них можно что-то выручить. Впрочем, это уже совсем другая история.
|
18
TODO
18
TODO
@ -27,7 +27,7 @@ Assets and i18n:
|
||||
ECS & engine:
|
||||
- implement time queue (how to deal with closures?) (?) github.com/thefish/scheduleq - get rid od time.Now inside
|
||||
- move all rendering to systems
|
||||
- try to move input handling to systems
|
||||
+ try to move input handling to systems
|
||||
|
||||
Dungeon and branches:
|
||||
General:
|
||||
@ -53,7 +53,7 @@ Combat:
|
||||
- mass
|
||||
- damage calculated from:
|
||||
kinetic energy is calculated by mass / speed / material density (p = mv) масса на скорость = кинетическая энергия
|
||||
next you determine target and its subpart (accuracy appied)
|
||||
next you determine target and its subpart (accuracy applied)
|
||||
next we calculate the area, on which kinetic energy is applied (determined by piercing, hacking, crushing damage profile) находим площадь
|
||||
next we calculate
|
||||
|
||||
@ -81,5 +81,17 @@ Combat:
|
||||
|
||||
Quest engine:
|
||||
- look at parsers like URQL etc
|
||||
- distorted Aschenputtel story / partisans / rapist prince / Grey Mountains
|
||||
- distorted Aschenputtel story / partisans / rapist prince / Grey Mountains / No gold
|
||||
|
||||
|
||||
No Gold in Gery Mountains / Kim Newman
|
||||
|
||||
- Drakenfells castle //location
|
||||
- Greteschele // char / rogue / charred zombie
|
||||
- Yom Lamprecht // char / rogue /
|
||||
- Tiley manor(?) // location / княжество
|
||||
- Melissa d'Acu // char / small girl
|
||||
|
||||
|
||||
|
||||
|
||||
|
26
assets/materials/commons.json
Normal file
26
assets/materials/commons.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"material_flags": [
|
||||
{
|
||||
"metal": {
|
||||
"conducts_elictricity": true,
|
||||
"blocks_liquid": true,
|
||||
"acid_resistant": true,
|
||||
"blocks_gas": true,
|
||||
"flammable": false,
|
||||
"conducts_heat": true,
|
||||
"radiates": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"wood": {
|
||||
"conducts_elictricity": false,
|
||||
"blocks_liquid": true,
|
||||
"acid_resistant": false,
|
||||
"blocks_gas": true,
|
||||
"flammable": true,
|
||||
"conducts_heat": false,
|
||||
"radiates": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
28
assets/materials/metals/metals.json
Normal file
28
assets/materials/metals/metals.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"materials": [
|
||||
{
|
||||
"steel": {
|
||||
"name": "steel",
|
||||
"material_flags": {
|
||||
"$ref": "#/material_flags/metal"
|
||||
},
|
||||
"density": "7800",
|
||||
"fracture_toughness": "30",
|
||||
"melting_point": "1400",
|
||||
"boiling_point": "3200"
|
||||
}
|
||||
},
|
||||
{
|
||||
"iron": {
|
||||
"name": "iron",
|
||||
"material_flags": {
|
||||
"$ref": "#/material_flags/metal"
|
||||
},
|
||||
"density": "7800",
|
||||
"fracture_toughness": "12",
|
||||
"melting_point": "1400",
|
||||
"boiling_point": "3200"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
15
assets/materials/misc.json
Normal file
15
assets/materials/misc.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"material_flags": [
|
||||
{
|
||||
"acme-fiber": {
|
||||
"conducts_elictricity": false,
|
||||
"blocks_liquid": true,
|
||||
"acid_resistant": true,
|
||||
"blocks_gas": true,
|
||||
"flammable": false,
|
||||
"conducts_heat": false,
|
||||
"radiates": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
16
assets/materials/wood/wood.json
Normal file
16
assets/materials/wood/wood.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"materials": [
|
||||
{
|
||||
"oakwood": {
|
||||
"name": "testoakwood",
|
||||
"material_flags": {
|
||||
"$ref": "#/material_flags/wood"
|
||||
},
|
||||
"density": "700",
|
||||
"fracture_toughness": "4.5",
|
||||
"melting_point": "600",
|
||||
"boiling_point": null
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
460
cmd/game/main.go
460
cmd/game/main.go
@ -1,26 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/ecs"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap/mapgens"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/gamestate"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/items"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/mob"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/mob/movement"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/screens"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
"lab.zaar.be/thefish/alchemyst-go/ui"
|
||||
"lab.zaar.be/thefish/alchemyst-go/ui/mainwindow"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util/appctx"
|
||||
blt "lab.zaar.be/thefish/bearlibterminal"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
"context"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/ecs"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap/mapgens"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/gamestate"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/items"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/mob"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/mob/movement"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/screens"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
"lab.zaar.be/thefish/alchemyst-go/ui"
|
||||
"lab.zaar.be/thefish/alchemyst-go/ui/mainwindow"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util/appctx"
|
||||
blt "lab.zaar.be/thefish/bearlibterminal"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var modifiers = []int{blt.TK_SHIFT, blt.TK_ALT, blt.TK_CONTROL}
|
||||
@ -28,12 +28,12 @@ var modifiers = []int{blt.TK_SHIFT, blt.TK_ALT, blt.TK_CONTROL}
|
||||
// Рецепт чтобы убежать от [fatal] 'refresh' was not called from the main thread
|
||||
// https://github.com/golang/go/wiki/LockOSThread
|
||||
func init() {
|
||||
runtime.LockOSThread()
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
//we can run logic in separate goroutines
|
||||
//
|
||||
// go doSometing(State,...)
|
||||
// go doSomething(State,...)
|
||||
//
|
||||
//and there we go like this:
|
||||
// func doSomething(State main.GameState, args...) {
|
||||
@ -45,252 +45,254 @@ func init() {
|
||||
// }
|
||||
|
||||
var State = gamestate.GameState{
|
||||
Mainfunc: make(chan func()),
|
||||
Exit: make(chan struct{}, 1),
|
||||
Input: make(chan string, 1),
|
||||
RawInput: make(chan int, 1),
|
||||
FovRecompute: make(chan struct{}, 1),
|
||||
Redraw: make(chan struct{}, 1),
|
||||
Mainfunc: make(chan func()),
|
||||
Exit: make(chan struct{}, 1),
|
||||
Input: make(chan string, 1),
|
||||
RawInput: make(chan int, 1),
|
||||
FovRecompute: make(chan struct{}, 1),
|
||||
Redraw: make(chan struct{}, 1),
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
config := util.LoadConfig()
|
||||
var logLevels = map[string]zerolog.Level{"debug": zerolog.DebugLevel, "info": zerolog.InfoLevel, "warn": zerolog.WarnLevel}
|
||||
var logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}).Level(logLevels[config.Verbosity])
|
||||
config := util.LoadConfig()
|
||||
var logLevels = map[string]zerolog.Level{"debug": zerolog.DebugLevel, "info": zerolog.InfoLevel, "warn": zerolog.WarnLevel}
|
||||
var logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}).Level(logLevels[config.Verbosity])
|
||||
|
||||
// set up context
|
||||
mainCtx := appctx.NewClientContext(config, &logger)
|
||||
// set up context
|
||||
appctx.NewClientContext(config, &logger)
|
||||
mainCtx := appctx.ClientState
|
||||
|
||||
//set up main window
|
||||
mw := mainwindow.Init(mainCtx)
|
||||
defer mw.Close()
|
||||
//set up main window
|
||||
mw := mainwindow.Init(mainCtx)
|
||||
defer mw.Close()
|
||||
|
||||
setupLayers(mw)
|
||||
setupLayers(mw)
|
||||
|
||||
//set up input decoder
|
||||
go decodeInput(mainCtx, mw.GetLayer("base"))
|
||||
//set up input decoder
|
||||
go decodeInput(mainCtx, mw.GetLayer("base"))
|
||||
|
||||
//fixme set up (load / generate) level - move to game / enter or title / exit
|
||||
//level, rooms := _default.DefaultGen(mainCtx, gamemap.NewLevel(mainCtx, "test", 1))
|
||||
//level, rooms := mapgens.DelaunayMstGen(mainCtx, gamemap.NewLevel(mainCtx, "test", 1))
|
||||
level, rooms := mapgens.DelaunayMstExtGen(mainCtx, gamemap.NewLevel(mainCtx, "test", 1))
|
||||
//level, rooms := mapgens.DelaunayPureGen(mainCtx, gamemap.NewLevel(mainCtx, "test", 1))
|
||||
State.Level = level
|
||||
//fixme set up (load / generate) level - move to game / enter or title / exit
|
||||
level, rooms := mapgens.DefaultGen(gamemap.NewLevel("test", 1))
|
||||
//level, rooms := mapgens.DelaunayMstGen(mainCtx, gamemap.NewLevel(mainCtx, "test", 1))
|
||||
//level, rooms := mapgens.DelaunayMstExtGen(mainCtx, gamemap.NewLevel(mainCtx, "test", 1))
|
||||
//level, rooms := mapgens.DelaunayPureGen(mainCtx, gamemap.NewLevel(mainCtx, "test", 1))
|
||||
State.Level = level
|
||||
|
||||
sidebarWidth := 0
|
||||
sidebarWidth := 0
|
||||
|
||||
//Set up viewport
|
||||
vp := mainwindow.NewViewPort(sidebarWidth, 0, (mw.W - sidebarWidth), (mw.H - 0))
|
||||
//Set up viewport
|
||||
vp := mainwindow.NewViewPort(sidebarWidth, 0, (mw.W - sidebarWidth), (mw.H - 0))
|
||||
|
||||
//set up controller
|
||||
//set up controller
|
||||
|
||||
controller := ecs.NewController(mainCtx)
|
||||
controller := ecs.NewController()
|
||||
|
||||
controller.MapComponentClass(ecs.CoordsComponent, types.Coords{})
|
||||
controller.MapComponentClass(ecs.AppearanceComponent, types.Appearance{})
|
||||
controller.MapComponentClass(ecs.MobComponent, mob.Mob{})
|
||||
controller.MapComponentClass(ecs.MoveableComponent, movement.Moveable{})
|
||||
controller.MapComponentClass(ecs.CarriedComponent, movement.Moveable{})
|
||||
controller.MapComponentClass(ecs.UsableComponent, movement.Moveable{})
|
||||
controller.MapComponentClass(ecs.BackpackComponent, items.Backpack{})
|
||||
controller.MapComponentClass(ecs.CoordsComponent, types.Coords{})
|
||||
controller.MapComponentClass(ecs.AppearanceComponent, types.Appearance{})
|
||||
controller.MapComponentClass(ecs.MobComponent, mob.Mob{})
|
||||
controller.MapComponentClass(ecs.MoveableComponent, movement.Moveable{})
|
||||
controller.MapComponentClass(ecs.CarriedComponent, movement.Moveable{})
|
||||
controller.MapComponentClass(ecs.UsableComponent, movement.Moveable{})
|
||||
controller.MapComponentClass(ecs.BackpackComponent, items.Backpack{})
|
||||
|
||||
moveable := movement.Moveable{
|
||||
Controller: controller,
|
||||
Level: level,
|
||||
}
|
||||
moveable := movement.Moveable{
|
||||
Controller: controller,
|
||||
Level: level,
|
||||
}
|
||||
|
||||
items.Init(controller)
|
||||
items.Init(controller)
|
||||
|
||||
bp := items.Backpack{MaxMass: 100, MaxBulk: 100}
|
||||
bp := items.Backpack{MaxMass:100, MaxBulk:100}
|
||||
|
||||
//Set up Screen Manager
|
||||
screenMgr := types.NewScreenManager(mainCtx)
|
||||
screenMgr.AddScreen("title", screens.NewTitleScreen(mw, screenMgr))
|
||||
screenMgr.AddScreen("game", screens.NewGameScreen(mainCtx, mw, &State, vp, controller, screenMgr))
|
||||
screenMgr.AddScreen("help", screens.NewMenuScreen(
|
||||
mw,
|
||||
screenMgr,
|
||||
"Help",
|
||||
"Keybindings:",
|
||||
//"[color=yellow]Note[/color]: Many of these are not implemented yet",
|
||||
"[color=yellow]Note[/color]: Many of these are not implemented yet",
|
||||
types.NewCenteredRect(mw.Rect, 50, 15),
|
||||
true).
|
||||
SetBgColor("#ef1d494f").
|
||||
SetFgColor("white").
|
||||
SetItems([]interface{}{
|
||||
"hjklyubn, NumPad 12346789, arrow keys - move",
|
||||
"s or . - pass turn",
|
||||
"g or , - pick up item",
|
||||
"i - inventory",
|
||||
"? - this screen",
|
||||
"Ctrl+q - exit",
|
||||
"f or F - fire or throw weapon",
|
||||
"z or Z - cast a spell",
|
||||
"p - pray",
|
||||
"Ctrl+p - message log",
|
||||
}).MakeList(),
|
||||
)
|
||||
//Set up Screen Manager
|
||||
screenMgr := types.NewScreenManager()
|
||||
screenMgr.AddScreen("title", screens.NewTitleScreen(mw, screenMgr))
|
||||
screenMgr.AddScreen("game", screens.NewGameScreen(mw, &State, vp, controller, screenMgr))
|
||||
screenMgr.AddScreen("help", screens.NewMenuScreen(
|
||||
mw,
|
||||
screenMgr,
|
||||
"Help",
|
||||
"Keybindings:",
|
||||
//"[color=yellow]Note[/color]: Many of these are not implemented yet",
|
||||
"[color=yellow]Note[/color]: Many of these are not implemented yet",
|
||||
types.NewCenteredRect(mw.Rect, 50, 15),
|
||||
true, ).
|
||||
SetBgColor("#ef1d494f").
|
||||
SetFgColor("white").
|
||||
SetItems([]interface{}{
|
||||
"hjklyubn, NumPad 12346789, arrow keys - move",
|
||||
"s or . - pass turn",
|
||||
"g or , - pick up item",
|
||||
"i - inventory",
|
||||
"? - this screen",
|
||||
"Ctrl+q - exit",
|
||||
"f or F - fire weapon or throw item",
|
||||
"z or Z - cast a spell",
|
||||
"p - pray",
|
||||
"Ctrl+p - message log",
|
||||
}).MakeList(),
|
||||
)
|
||||
|
||||
inv := screens.InventoryScreen{
|
||||
MenuScreen: screens.NewMenuScreen(
|
||||
mw,
|
||||
screenMgr,
|
||||
"Inventory",
|
||||
"Items in your backpack:",
|
||||
//"[color=yellow]Note[/color]: Many of these are not implemented yet",
|
||||
"",
|
||||
types.NewCenteredRect(mw.Rect, 70, 25),
|
||||
true).
|
||||
SetBgColor("#ef305c70").
|
||||
SetFgColor("white"),
|
||||
}
|
||||
inv := screens.InventoryScreen{
|
||||
MenuScreen: screens.NewMenuScreen(
|
||||
mw,
|
||||
screenMgr,
|
||||
"Inventory",
|
||||
"Items in your backpack:",
|
||||
//"[color=yellow]Note[/color]: Many of these are not implemented yet",
|
||||
"",
|
||||
types.NewCenteredRect(mw.Rect, 70, 25),
|
||||
true, ).
|
||||
SetBgColor("#ef305c70").
|
||||
SetFgColor("white"),
|
||||
}
|
||||
|
||||
screenMgr.AddScreen("devmenu", screens.NewDevmenuScreen(
|
||||
mainCtx,
|
||||
mw,
|
||||
controller,
|
||||
screenMgr,
|
||||
&State,
|
||||
types.NewCenteredRect(mw.Rect, 70, 25),
|
||||
true,
|
||||
).SetBgColor("#ef6d559d").
|
||||
SetFgColor("white"),
|
||||
)
|
||||
screenMgr.AddScreen("devmenu", screens.NewDevmenuScreen(
|
||||
mainCtx,
|
||||
mw,
|
||||
controller,
|
||||
screenMgr,
|
||||
&State,
|
||||
types.NewCenteredRect(mw.Rect, 70, 25),
|
||||
true,
|
||||
).SetBgColor("#ef6d559d").
|
||||
SetFgColor("white"),
|
||||
)
|
||||
|
||||
screenMgr.SetScreenByName("title")
|
||||
screenMgr.SetScreenByName("title")
|
||||
|
||||
//fixme set up (load / generate) player - move to game / enter or title / exit
|
||||
player := controller.CreateEntity([]ecs.Component{})
|
||||
//fixme set up (load / generate) player - move to game / enter or title / exit
|
||||
player := controller.CreateEntity([]ecs.Component{})
|
||||
|
||||
controller.AddComponent(player, types.Appearance{
|
||||
Glyph: types.PlainGlyphHolder{"@"},
|
||||
ColorSet: types.TileColorSet{
|
||||
Fg: types.PlainColorHolder{255, 255, 255, 255},
|
||||
},
|
||||
})
|
||||
controller.AddComponent(player, types.Appearance{
|
||||
Glyph: types.PlainGlyphHolder{"@"},
|
||||
ColorSet: types.TileColorSet{
|
||||
Fg: types.PlainColorHolder{255, 255, 255, 255},
|
||||
},
|
||||
})
|
||||
|
||||
controller.AddComponent(player, rooms[0].Center) //implicit Coords
|
||||
controller.AddComponent(player, moveable)
|
||||
controller.AddComponent(player, bp)
|
||||
controller.AddComponent(player, rooms[0].Center) //implicit Coords
|
||||
controller.AddComponent(player, moveable)
|
||||
controller.AddComponent(player, bp)
|
||||
|
||||
//fixme adding items
|
||||
potion := controller.CreateEntity([]ecs.Component{})
|
||||
controller.AddComponent(potion, types.Appearance{
|
||||
Glyph: types.PlainGlyphHolder{"!"},
|
||||
ColorSet: types.TileColorSet{
|
||||
Fg: types.PlainColorHolder{255, 55, 255, 222},
|
||||
},
|
||||
})
|
||||
controller.AddComponent(potion, rooms[0].Center) //implicit Coords
|
||||
controller.AddComponent(potion, items.Carried{Mass: 5, Bulk: 3})
|
||||
controller.AddComponent(potion, items.Usable{})
|
||||
controller.AddComponent(potion, items.Consumable{})
|
||||
controller.AddComponent(potion, ecs.Named{Name: "first potion"})
|
||||
//fixme adding items
|
||||
potion := controller.CreateEntity([]ecs.Component{})
|
||||
controller.AddComponent(potion, types.Appearance{
|
||||
Glyph: types.PlainGlyphHolder{"!"},
|
||||
ColorSet: types.TileColorSet{
|
||||
Fg: types.PlainColorHolder{255, 55, 255, 222},
|
||||
},
|
||||
})
|
||||
controller.AddComponent(potion, rooms[0].Center) //implicit Coords
|
||||
controller.AddComponent(potion, items.Carried{Mass:5, Bulk:3}) //fixme generate from blueprint!
|
||||
controller.AddComponent(potion, items.Usable{})
|
||||
controller.AddComponent(potion, items.Consumable{})
|
||||
controller.AddComponent(potion, ecs.Named{Name:"first potion"})
|
||||
|
||||
potion2 := controller.CreateEntity([]ecs.Component{})
|
||||
controller.AddComponent(potion2, types.Appearance{
|
||||
Glyph: types.PlainGlyphHolder{"!"},
|
||||
ColorSet: types.TileColorSet{
|
||||
Fg: types.PlainColorHolder{255, 222, 255, 55},
|
||||
},
|
||||
})
|
||||
controller.AddComponent(potion2, rooms[1].Center) //implicit Coords
|
||||
controller.AddComponent(potion2, items.Carried{Mass: 5, Bulk: 3})
|
||||
controller.AddComponent(potion2, items.Usable{})
|
||||
controller.AddComponent(potion2, items.Consumable{})
|
||||
controller.AddComponent(potion2, ecs.Named{Name: "second potion"})
|
||||
//fixme end setting up items
|
||||
potion2 := controller.CreateEntity([]ecs.Component{})
|
||||
controller.AddComponent(potion2, types.Appearance{
|
||||
Glyph: types.PlainGlyphHolder{Glyph: "!"},
|
||||
ColorSet: types.TileColorSet{
|
||||
Fg: types.PlainColorHolder{A: 255, R: 222, G: 255, B: 55},
|
||||
},
|
||||
})
|
||||
controller.AddComponent(potion2, rooms[1].Center) //implicit Coords
|
||||
controller.AddComponent(potion2, items.Carried{Mass:5, Bulk:3})
|
||||
controller.AddComponent(potion2, items.Usable{})
|
||||
controller.AddComponent(potion2, items.Consumable{})
|
||||
controller.AddComponent(potion2, ecs.Named{Name:"second potion"})
|
||||
//fixme end setting up items
|
||||
|
||||
State.Player = player
|
||||
State.Controller = controller
|
||||
State.Player = player
|
||||
State.Controller = controller
|
||||
|
||||
screenMgr.AddScreen("inventory", inv.MakeInverntory(player))
|
||||
screenMgr.AddScreen("inventory", inv.MakeInverntory(player))
|
||||
|
||||
//but every call to bearlibterminal must be wrapped to closure and passed to mainfunc
|
||||
var exit = false
|
||||
for !exit {
|
||||
|
||||
select {
|
||||
case State.RawInput <- ui.ReadKeyCode():
|
||||
break
|
||||
case pressed := <-State.Input:
|
||||
screenMgr.CurrentScreen.HandleInput(pressed)
|
||||
break
|
||||
//case f := <-State.mainfunc:
|
||||
// f()
|
||||
// break
|
||||
case <-State.Exit:
|
||||
appctx.Logger(mainCtx).Warn().Msg("quitting NOW")
|
||||
exit = true
|
||||
break
|
||||
// не оставляйте default в бесконечном select {} - сожрет всё CPU
|
||||
default:
|
||||
screenMgr.CurrentScreen.Render()
|
||||
blt.Layer(0) //return to base layer
|
||||
blt.Refresh()
|
||||
}
|
||||
//but every call to bearlibterminal must be wrapped to closure and passed to mainfunc
|
||||
var exit = false
|
||||
for !exit {
|
||||
|
||||
}
|
||||
appctx.Logger(mainCtx).Info().Msg("pre-shutdown sequence")
|
||||
select {
|
||||
case State.RawInput <- ui.ReadKeyCode():
|
||||
break
|
||||
case pressed := <-State.Input:
|
||||
screenMgr.CurrentScreen.HandleInput(pressed)
|
||||
break
|
||||
//case f := <-State.mainfunc:
|
||||
// f()
|
||||
// break
|
||||
case <-State.Exit:
|
||||
appctx.Logger().Warn().Msg("quitting NOW")
|
||||
exit = true
|
||||
break
|
||||
// не оставляйте default в бесконечном select {} - сожрет всё CPU
|
||||
default:
|
||||
screenMgr.CurrentScreen.Render()
|
||||
blt.Layer(0) //return to base layer
|
||||
blt.Refresh()
|
||||
}
|
||||
|
||||
}
|
||||
appctx.Logger().Info().Msg("pre-shutdown sequence")
|
||||
}
|
||||
|
||||
func setupLayers(mainwindow *mainwindow.MainWindow) {
|
||||
mainwindow.AddLayer("base", 0, "white")
|
||||
mainwindow.AddLayer("overlay", 1, "white")
|
||||
mainwindow.AddLayer("menubg", 2, "white")
|
||||
mainwindow.AddLayer("menu", 3, "white")
|
||||
mainwindow.AddLayer("base", 0, "white")
|
||||
mainwindow.AddLayer("overlay", 1, "white")
|
||||
mainwindow.AddLayer("menubg", 2, "white")
|
||||
mainwindow.AddLayer("menu", 3, "white")
|
||||
}
|
||||
|
||||
func decodeInput(ctx context.Context, baseLayer *mainwindow.Layer) {
|
||||
var exit = false
|
||||
var waitForWCspam = true
|
||||
for !exit {
|
||||
select {
|
||||
case keycode := <-State.RawInput:
|
||||
if keycode == blt.TK_NONE {
|
||||
continue
|
||||
}
|
||||
if keycode == blt.TK_CLOSE && !waitForWCspam {
|
||||
appctx.Logger(ctx).Warn().Msg("exiting on window close...")
|
||||
State.Exit <- struct{}{}
|
||||
appctx.Logger(ctx).Warn().Msg("...done")
|
||||
return
|
||||
}
|
||||
var pressed = ""
|
||||
var isModifier, _ = util.IntInSlice(keycode, modifiers)
|
||||
if !isModifier {
|
||||
var exit = false
|
||||
var waitForWCspam = true
|
||||
for !exit {
|
||||
select {
|
||||
case keycode := <-State.RawInput:
|
||||
if keycode == blt.TK_NONE {
|
||||
continue
|
||||
}
|
||||
if keycode == blt.TK_CLOSE && !waitForWCspam {
|
||||
appctx.Logger().Warn().Msg("exiting on window close...")
|
||||
State.Exit <- struct{}{}
|
||||
appctx.Logger().Warn().Msg("...done")
|
||||
return
|
||||
}
|
||||
var pressed = ""
|
||||
var isModifier, _ = util.IntInSlice(keycode, modifiers)
|
||||
if !isModifier {
|
||||
|
||||
pressed = ui.Scancodemap[keycode]
|
||||
pressed = ui.Scancodemap[keycode]
|
||||
|
||||
if blt.Check(blt.TK_SHIFT) != 0 {
|
||||
pressed = "Shift+" + pressed
|
||||
}
|
||||
if blt.Check(blt.TK_ALT) != 0 {
|
||||
pressed = "Alt+" + pressed
|
||||
}
|
||||
if blt.Check(blt.TK_CONTROL) != 0 {
|
||||
pressed = "Ctrl+" + pressed
|
||||
}
|
||||
if blt.Check(blt.TK_SHIFT) != 0 {
|
||||
pressed = "Shift+" + pressed
|
||||
}
|
||||
if blt.Check(blt.TK_ALT) != 0 {
|
||||
pressed = "Alt+" + pressed
|
||||
}
|
||||
if blt.Check(blt.TK_CONTROL) != 0 {
|
||||
pressed = "Ctrl+" + pressed
|
||||
}
|
||||
|
||||
//global hotkeys
|
||||
switch pressed {
|
||||
case "Ctrl+q":
|
||||
//fallthrough
|
||||
//case "Escape":
|
||||
appctx.Logger(ctx).Info().Msg("exiting on quit command...")
|
||||
State.Exit <- struct{}{}
|
||||
appctx.Logger(ctx).Info().Msg("...done")
|
||||
exit = true
|
||||
return
|
||||
default:
|
||||
if pressed != "" {
|
||||
waitForWCspam = false
|
||||
//global hotkeys
|
||||
switch pressed {
|
||||
case "Ctrl+q":
|
||||
//fallthrough
|
||||
//case "Escape":
|
||||
appctx.Logger().Info().Msg("exiting on quit command...")
|
||||
State.Exit <- struct{}{}
|
||||
appctx.Logger().Info().Msg("...done")
|
||||
exit = true
|
||||
return
|
||||
default:
|
||||
if pressed != "" {
|
||||
waitForWCspam = false;
|
||||
State.Input <- pressed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "v0.0.1.7",
|
||||
"version": "v0.0.1.7-29-g4f18b6d",
|
||||
"title": "Alchemyst",
|
||||
"sizeX": 100,
|
||||
"sizeY": 47,
|
||||
|
@ -25,7 +25,7 @@ func TestDelaunay(t *testing.T) {
|
||||
{types.Coords{10, 10}, types.Coords{30, 10}},
|
||||
}
|
||||
|
||||
result := delaunay.GetMst(coords, 100, 100, 0)
|
||||
result := delaunay.GetMst(coords, 100, 100, 100 )
|
||||
|
||||
for idx, _ := range result {
|
||||
if result[idx] != expected[idx] {
|
||||
|
@ -3,13 +3,11 @@ package ecs
|
||||
// ECS system by jcerise, github.com/jcerise/gogue
|
||||
|
||||
import (
|
||||
"context"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util/appctx"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
ctx context.Context
|
||||
systems map[string]System
|
||||
sortedSystems map[int][]System
|
||||
priorityKeys []int
|
||||
@ -23,8 +21,8 @@ type Controller struct {
|
||||
}
|
||||
|
||||
// NewController is a convenience/constructor method to properly initialize a new processor
|
||||
func NewController(ctx context.Context) *Controller {
|
||||
controller := Controller{ctx: ctx}
|
||||
func NewController() *Controller {
|
||||
controller := Controller{}
|
||||
controller.systems = make(map[string]System)
|
||||
controller.sortedSystems = make(map[int][]System)
|
||||
controller.priorityKeys = []int{}
|
||||
@ -80,7 +78,7 @@ func (c *Controller) GetMappedComponentClass(componentName string) Component {
|
||||
return c.componentMap[componentName]
|
||||
} else {
|
||||
// TODO: Add better (read: actual) error handling here
|
||||
appctx.Logger(c.ctx).Warn().Msgf("Component[%s] not registered on Controller.\n", componentName)
|
||||
appctx.Logger().Warn().Msgf("Component[%s] not registered on Controller.\n", componentName)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -140,15 +138,20 @@ func (c *Controller) GetEntities() map[Entity]map[string]Component {
|
||||
}
|
||||
|
||||
// GetEntitiesWithComponent returns a list of all entities with a given component attached
|
||||
// TODO: Allow for passing a list of components
|
||||
func (c *Controller) GetEntitiesWithComponent(componentType string) []Entity {
|
||||
func (c *Controller) GetEntitiesWithComponent(componentTypes... string) []Entity {
|
||||
entitiesWithComponent := make([]Entity, 0)
|
||||
for entity := range c.entities {
|
||||
if c.HasComponent(entity, componentType) {
|
||||
mustAddThis := true
|
||||
for _, componentType := range componentTypes {
|
||||
if !c.HasComponent(entity, componentType) {
|
||||
mustAddThis = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if mustAddThis {
|
||||
entitiesWithComponent = append(entitiesWithComponent, entity)
|
||||
}
|
||||
}
|
||||
|
||||
return entitiesWithComponent
|
||||
}
|
||||
|
||||
@ -208,7 +211,7 @@ func (c *Controller) AddSystem(system System, priority int) {
|
||||
c.sortedSystems[priority] = append(c.sortedSystems[priority], system)
|
||||
sort.Ints(c.priorityKeys)
|
||||
} else {
|
||||
appctx.Logger(c.ctx).Warn().Msgf("A system of type %v was already added to the controller %v!", systemType, c)
|
||||
appctx.Logger().Warn().Msgf("A system of type %v was already added to the controller %v!", systemType, c)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ func NewLevelRenderSystem(
|
||||
return trs
|
||||
}
|
||||
|
||||
//fixme add to screens/game Exit()
|
||||
// fixme add to screens/game Exit()
|
||||
func (trs LevelRenderSystem) Close() {
|
||||
trs.animateTiles.Stop()
|
||||
trs.animateTiles = nil //zero pointer to ticker
|
||||
@ -93,7 +93,7 @@ func (trs LevelRenderSystem) Process() {
|
||||
//terrain
|
||||
for y := 0; y < trs.Viewport.H; y++ {
|
||||
for x := 0; x < trs.Viewport.W; x++ {
|
||||
mapCoords := types.Coords{trs.Viewport.CameraCoords.X + x, trs.Viewport.CameraCoords.Y + y}
|
||||
mapCoords := types.Coords{X: trs.Viewport.CameraCoords.X + x, Y: trs.Viewport.CameraCoords.Y + y}
|
||||
|
||||
if trs.state.Level.InBounds(mapCoords) {
|
||||
tile := trs.state.Level.GetTile(mapCoords)
|
||||
|
@ -62,7 +62,7 @@ func (pp *precomputedPermissive) ComputeFov(coords types.Coords, radius int) {
|
||||
func (pp *precomputedPermissive) PrecomputeFovMap() {
|
||||
max := pp.MaxTorchRadius
|
||||
minusMax := (-1) * max
|
||||
zeroCoords := types.Coords{0, 0}
|
||||
zeroCoords := types.Coords{X: 0, Y: 0}
|
||||
var x, y int
|
||||
//fill list
|
||||
for x = minusMax; x < max+1; x++ {
|
||||
@ -70,7 +70,7 @@ func (pp *precomputedPermissive) PrecomputeFovMap() {
|
||||
if x == 0 && y == 0 {
|
||||
continue;
|
||||
}
|
||||
iterCoords := types.Coords{x, y}
|
||||
iterCoords := types.Coords{X: x, Y: y}
|
||||
distance := zeroCoords.DistanceTo(iterCoords)
|
||||
if distance <= float64(max) {
|
||||
pp.CellList = append(pp.CellList, &Cell{iterCoords, distance, nil})
|
||||
@ -101,7 +101,7 @@ func (pp *precomputedPermissive) PrecomputeFovMap() {
|
||||
roundedX := int(basic.Round(lineX))
|
||||
roundedY := int(basic.Round(lineY))
|
||||
|
||||
idx, cell, err := pp.FindByCoords(types.Coords{roundedX, roundedY})
|
||||
idx, cell, err := pp.FindByCoords(types.Coords{X: roundedX, Y: roundedY})
|
||||
if err != nil {
|
||||
//inexistent coord found
|
||||
break;
|
||||
|
@ -124,7 +124,9 @@ func (ps *precomputedShade) FindByCoords(c types.Coords) (int, *Cell, error) {
|
||||
|
||||
func (ps *precomputedShade) IsInFov(coords types.Coords) bool {
|
||||
rc := ps.fromLevelCoords(coords)
|
||||
if rc.X == 0 && rc.Y ==0 {return true}
|
||||
if rc.X == 0 && rc.Y == 0 {
|
||||
return true
|
||||
}
|
||||
_, cell, err := ps.FindByCoords(rc)
|
||||
if err != nil {
|
||||
return false
|
||||
@ -143,15 +145,15 @@ func (ps *precomputedShade) Init() {
|
||||
func (ps *precomputedShade) PrecomputeFovMap() {
|
||||
max := ps.MaxTorchRadius
|
||||
minusMax := (-1) * max
|
||||
zeroCoords := types.Coords{0, 0}
|
||||
zeroCoords := types.Coords{X: 0, Y: 0}
|
||||
var x, y int
|
||||
//fill list
|
||||
for x = minusMax; x < max+1; x++ {
|
||||
for y = minusMax; y < max+1; y++ {
|
||||
if x == 0 && y == 0 {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
iterCoords := types.Coords{x, y}
|
||||
iterCoords := types.Coords{X: x, Y: y}
|
||||
distance := zeroCoords.DistanceTo(iterCoords)
|
||||
if distance <= float64(max) {
|
||||
ps.CellList = append(ps.CellList, &Cell{iterCoords, distance, nil, 0})
|
||||
@ -180,11 +182,11 @@ func (ps *precomputedShade) PrecomputeFovMap() {
|
||||
roundedX := int(basic.Round(lineX))
|
||||
roundedY := int(basic.Round(lineY))
|
||||
|
||||
_, cell, err := ps.FindByCoords(types.Coords{roundedX, roundedY})
|
||||
_, cell, err := ps.FindByCoords(types.Coords{X: roundedX, Y: roundedY})
|
||||
|
||||
if err != nil {
|
||||
//inexistent coord found
|
||||
break;
|
||||
break
|
||||
}
|
||||
cell.occludedAngles = unique(append(cell.occludedAngles, i))
|
||||
}
|
||||
@ -219,7 +221,7 @@ func (ps *precomputedShade) recalc(level *gamemap.Level, initCoords types.Coords
|
||||
i := 0
|
||||
prevDistance := 0.0
|
||||
for !bytes.Equal(currentShade, fullShade) {
|
||||
if (i == len(ps.CellList)-1) {
|
||||
if i == len(ps.CellList)-1 {
|
||||
break
|
||||
}
|
||||
cell := ps.CellList[i]
|
||||
@ -242,7 +244,7 @@ func (ps *precomputedShade) recalc(level *gamemap.Level, initCoords types.Coords
|
||||
|
||||
if level.GetTile(lc).BlocksSight && ps.LightWalls {
|
||||
//if (nextShade[angle] == 0 && currentShade[angle] == 0) {
|
||||
if (nextShade[angle] == 0) {
|
||||
if nextShade[angle] == 0 {
|
||||
level.GetTile(lc).Visible = true
|
||||
level.GetTile(lc).Explored = true
|
||||
}
|
||||
@ -266,7 +268,7 @@ func (ps *precomputedShade) ComputeFov(level *gamemap.Level, initCoords types.Co
|
||||
//fmt.Printf("\n coords: %v, distance: %f, lit: %d", cell.Coords, cell.distance, cell.lit)
|
||||
cs, err := ps.toLevelCoords(level, initCoords, cell.Coords)
|
||||
if cell.lit > 0 && cell.lit > MIN_LIT_TO_BE_VISIBLE {
|
||||
//if cell.lit > 0 && cell.lit / (ps.MaxTorchRadius - int(cell.distance - 0.4) - 1) > MIN_LIT_TO_BE_VISIBLE {
|
||||
//if cell.lit > 0 && cell.lit / (ps.MaxTorchRadius - int(cell.distance - 0.4) - 1) > MIN_LIT_TO_BE_VISIBLE {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -298,7 +300,7 @@ func (ps *precomputedShade) ComputeFov(level *gamemap.Level, initCoords types.Co
|
||||
}
|
||||
|
||||
func (ps *precomputedShade) toLevelCoords(level *gamemap.Level, initCoords, relativeCoords types.Coords) (types.Coords, error) {
|
||||
realCoords := types.Coords{initCoords.X + relativeCoords.X, initCoords.Y + relativeCoords.Y}
|
||||
realCoords := types.Coords{X: initCoords.X + relativeCoords.X, Y: initCoords.Y + relativeCoords.Y}
|
||||
if !level.InBounds(realCoords) {
|
||||
return types.Coords{}, errOutOfBounds
|
||||
}
|
||||
@ -306,7 +308,7 @@ func (ps *precomputedShade) toLevelCoords(level *gamemap.Level, initCoords, rela
|
||||
}
|
||||
|
||||
func (ps *precomputedShade) fromLevelCoords(lc types.Coords) types.Coords {
|
||||
relativeCoords := types.Coords{lc.X - ps.originCoords.X, lc.Y - ps.originCoords.Y}
|
||||
relativeCoords := types.Coords{X: lc.X - ps.originCoords.X, Y: lc.Y - ps.originCoords.Y}
|
||||
return relativeCoords
|
||||
}
|
||||
|
||||
|
@ -8,12 +8,12 @@ import (
|
||||
)
|
||||
|
||||
func TestPsDistance(t *testing.T) {
|
||||
iterCoords := types.Coords{0, 0}
|
||||
iterCoords := types.Coords{X: 0, Y: 0}
|
||||
|
||||
fmt.Printf("\n dto: \t %v", iterCoords.DistanceTo(types.Coords{0, 1}))
|
||||
fmt.Printf("\n dto: \t %v", iterCoords.DistanceTo(types.Coords{0, 5}))
|
||||
fmt.Printf("\n dto: \t %v", iterCoords.DistanceTo(types.Coords{3, 3}))
|
||||
fmt.Printf("\n dto: \t %v", iterCoords.DistanceTo(types.Coords{100, 0}))
|
||||
fmt.Printf("\n dto: \t %v", iterCoords.DistanceTo(types.Coords{X: 0, Y: 1}))
|
||||
fmt.Printf("\n dto: \t %v", iterCoords.DistanceTo(types.Coords{X: 0, Y: 5}))
|
||||
fmt.Printf("\n dto: \t %v", iterCoords.DistanceTo(types.Coords{X: 3, Y: 3}))
|
||||
fmt.Printf("\n dto: \t %v", iterCoords.DistanceTo(types.Coords{X: 100, Y: 0}))
|
||||
}
|
||||
|
||||
func TestPrecompShade(t *testing.T) {
|
||||
@ -44,7 +44,7 @@ func TestPrecompShade(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
playerCoords := types.Coords{10, 10}
|
||||
playerCoords := types.Coords{X: 10, Y: 10}
|
||||
|
||||
level.SetTileByXY(8, 12, gamemap.NewWall())
|
||||
level.SetTileByXY(10, 8, gamemap.NewWall())
|
||||
|
@ -1,7 +1,8 @@
|
||||
package gamemap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/ecs"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util/appctx"
|
||||
@ -14,7 +15,6 @@ var mapHeight = 90
|
||||
|
||||
type Level struct {
|
||||
types.Rect
|
||||
ctx context.Context
|
||||
Name string
|
||||
Branch string
|
||||
Depth int
|
||||
@ -30,7 +30,7 @@ func (l *Level) GetTileNbs (coords types.Coords) []*Tile {
|
||||
result := make([]*Tile,0)
|
||||
for i := coords.X-1; i < coords.X+1; i++ {
|
||||
for j := coords.Y-1; j < coords.Y+1; j++ {
|
||||
nbc := types.Coords{i,j}
|
||||
nbc := types.Coords{X: i,Y: j}
|
||||
if l.InBounds(nbc){
|
||||
if nbc == coords {
|
||||
continue
|
||||
@ -66,34 +66,33 @@ func (l *Level) MakePassByXY (x,y int, tile *Tile) {
|
||||
func (l *Level) Put (x, y int, tileFunc interface{}) {
|
||||
tile := tileFunc.(func() *Tile)()
|
||||
if tile == nil {
|
||||
appctx.Logger(l.ctx).Fatal().Msgf("Got non-tile type to put into level: %v", tile)
|
||||
appctx.Logger().Fatal().Msgf("Got non-tile type to put into level: %v", tile)
|
||||
}
|
||||
if l.InBounds(types.Coords{x, y}) {
|
||||
if l.InBounds(types.Coords{X: x, Y: y}) {
|
||||
l.Tiles[y*l.W+x] = tile
|
||||
}
|
||||
}
|
||||
|
||||
func NewLevel(ctx context.Context, branch string, depth int) *Level {
|
||||
func NewLevel(branch string, depth int) *Level {
|
||||
l := &Level{
|
||||
ctx: ctx,
|
||||
Name: branch + string(depth),
|
||||
Name: fmt.Sprintf(branch, depth),
|
||||
Depth: depth,
|
||||
Rect: types.NewRect(0,0, mapWidth, mapHeight),
|
||||
}
|
||||
|
||||
l.Tiles = make([]*Tile, l.W*l.H)
|
||||
appctx.Logger(ctx).Debug().Msgf("Generating level of branch %s depth %d", branch, depth)
|
||||
appctx.Logger().Debug().Msgf("Generating level of branch %s depth %d", branch, depth)
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *Level) SetAllInvisible() {
|
||||
for idx, _ := range l.Tiles {
|
||||
for idx := range l.Tiles {
|
||||
l.Tiles[idx].Visible = false
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Level) SetAllVisible() {
|
||||
for idx, _ := range l.Tiles {
|
||||
for idx := range l.Tiles {
|
||||
l.Tiles[idx].Visible = true
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package mapgens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util"
|
||||
@ -14,7 +13,7 @@ var maxRoomSize = 22
|
||||
var maxrooms = 100
|
||||
|
||||
var fges = map[int]types.RectFill{
|
||||
1: types.RectFill{
|
||||
1: {
|
||||
Top: gamemap.NewWall,
|
||||
Bottom: gamemap.NewWall,
|
||||
Left: gamemap.NewWall,
|
||||
@ -26,7 +25,7 @@ var fges = map[int]types.RectFill{
|
||||
Body: gamemap.NewFloor,
|
||||
},
|
||||
|
||||
2: types.RectFill{
|
||||
2: {
|
||||
Top: gamemap.NewWaterTile,
|
||||
Bottom: gamemap.NewWaterTile,
|
||||
Left: gamemap.NewWaterTile,
|
||||
@ -39,9 +38,9 @@ var fges = map[int]types.RectFill{
|
||||
},
|
||||
}
|
||||
|
||||
func GetRandomRoomList(ctx context.Context, rng *util.RNG, l *gamemap.Level, maxRooms, minRoomSize, maxRoomSize int, ) []gamemap.Room{
|
||||
func GetRandomRoomList(rng *util.RNG, l *gamemap.Level, maxRooms, minRoomSize, maxRoomSize int, ) []gamemap.Room{
|
||||
rooms := make([]gamemap.Room, 0)
|
||||
pfLoader := gamemap.NewPrefabLoader(ctx)
|
||||
pfLoader := gamemap.NewPrefabLoader()
|
||||
pfRooms := pfLoader.PrefabRoomsList()
|
||||
|
||||
var fillage types.RectFill
|
||||
@ -54,7 +53,14 @@ func GetRandomRoomList(ctx context.Context, rng *util.RNG, l *gamemap.Level, max
|
||||
|
||||
|
||||
var newRoom = gamemap.Room{}
|
||||
if !prefabUsed || rng.Range(0, 5) > 3 {
|
||||
if prefabUsed && rng.Range(0, 5) <= 3 {
|
||||
newRoom = gamemap.NewRandomRectRoom(
|
||||
rng,
|
||||
rng.Range(minRoomSize, maxRoomSize),
|
||||
rng.Range(minRoomSize, maxRoomSize),
|
||||
fillage,
|
||||
)
|
||||
} else {
|
||||
//if prefabUsed {
|
||||
//prefab
|
||||
prefabUsed = true
|
||||
@ -68,20 +74,14 @@ func GetRandomRoomList(ctx context.Context, rng *util.RNG, l *gamemap.Level, max
|
||||
Mobs: r.Mobs,
|
||||
Connectors: make([]types.Coords,0),
|
||||
}
|
||||
for _, coord := range r.Connectors {
|
||||
newRoom.Connectors = append(newRoom.Connectors, coord)
|
||||
}
|
||||
} else {
|
||||
newRoom = gamemap.NewRandomRectRoom(
|
||||
rng,
|
||||
rng.Range(minRoomSize, maxRoomSize),
|
||||
rng.Range(minRoomSize, maxRoomSize),
|
||||
fillage,
|
||||
)
|
||||
newRoom.Connectors = append(newRoom.Connectors, r.Connectors...)
|
||||
// for _, coord := range r.Connectors {
|
||||
// newRoom.Connectors = append(newRoom.Connectors, coord)
|
||||
// }
|
||||
}
|
||||
where := types.Coords{
|
||||
rng.Range(1, l.W-2-newRoom.W),
|
||||
rng.Range(1, l.H-2-newRoom.H),
|
||||
X: rng.Range(1, l.W-2-newRoom.W),
|
||||
Y: rng.Range(1, l.H-2-newRoom.H),
|
||||
}
|
||||
|
||||
newRoom.MoveToCoords(where)
|
||||
@ -102,11 +102,12 @@ func GetRandomRoomList(ctx context.Context, rng *util.RNG, l *gamemap.Level, max
|
||||
return rooms
|
||||
}
|
||||
|
||||
func BlitToLevel (ctx context.Context, l *gamemap.Level, rooms[]gamemap.Room) {
|
||||
//fixme overlapping rooms
|
||||
func BlitToLevel (l *gamemap.Level, rooms[]gamemap.Room) {
|
||||
for _, room := range rooms {
|
||||
err := room.BlitToLevel(ctx, l)
|
||||
err := room.BlitToLevel(l)
|
||||
if err != nil {
|
||||
appctx.Logger(ctx).Err(err)
|
||||
appctx.Logger().Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -172,7 +173,7 @@ func DigHTunnel(l *gamemap.Level, x1, x2, y int) {
|
||||
finish = x1
|
||||
}
|
||||
for i := start; i <= finish; i++ {
|
||||
if l.InBounds(types.Coords{i, y}) {
|
||||
if l.InBounds(types.Coords{X: i, Y: y}) {
|
||||
l.MakePassByXY(i, y, gamemap.NewFloor())
|
||||
//l.Tiles[i][y] = gamemap.NewFloor()
|
||||
}
|
||||
@ -189,7 +190,7 @@ func DigVTunnel(l *gamemap.Level, y1, y2, x int) {
|
||||
finish = y1
|
||||
}
|
||||
for i := start; i <= finish; i++ {
|
||||
if l.InBounds(types.Coords{x, i}) {
|
||||
if l.InBounds(types.Coords{X: x, Y: i}) {
|
||||
l.MakePassByXY(x, i, gamemap.NewFloor())
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,9 @@ package mapgens
|
||||
import (
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util/appctx"
|
||||
)
|
||||
|
||||
func DefaultGen(ctx appctx.ClientCtx,l *gamemap.Level) (*gamemap.Level, []gamemap.Room) {
|
||||
func DefaultGen(l *gamemap.Level) (*gamemap.Level, []gamemap.Room) {
|
||||
|
||||
rng := util.NewRNG()
|
||||
|
||||
@ -17,9 +16,9 @@ func DefaultGen(ctx appctx.ClientCtx,l *gamemap.Level) (*gamemap.Level, []gamema
|
||||
}
|
||||
}
|
||||
|
||||
rooms := GetRandomRoomList(ctx, rng, l, maxrooms, minRoomSize, maxRoomSize)
|
||||
rooms := GetRandomRoomList(rng, l, maxrooms, minRoomSize, maxRoomSize)
|
||||
|
||||
BlitToLevel(ctx, l, rooms)
|
||||
BlitToLevel(l, rooms)
|
||||
|
||||
for idx, room := range rooms {
|
||||
if idx > 0 {
|
||||
|
@ -18,10 +18,10 @@ func DelaunayMstGen(ctx context.Context, l *gamemap.Level) (*gamemap.Level, []ga
|
||||
l.SetTileByXY(i, j, gamemap.NewWall())
|
||||
}
|
||||
}
|
||||
rooms := GetRandomRoomList(ctx, rng, l, maxrooms, minRoomSize, maxRoomSize)
|
||||
rooms := GetRandomRoomList(rng, l, maxrooms, minRoomSize, maxRoomSize)
|
||||
|
||||
|
||||
BlitToLevel(ctx, l, rooms)
|
||||
BlitToLevel(l, rooms)
|
||||
|
||||
centers := make([]types.Coords, 0)
|
||||
for _, room := range rooms {
|
||||
|
@ -4,11 +4,10 @@ import (
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/gamemap"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util/appctx"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util/delaunay"
|
||||
)
|
||||
|
||||
func DelaunayMstExtGen(ctx appctx.ClientCtx, l *gamemap.Level) (*gamemap.Level, []gamemap.Room) {
|
||||
func DelaunayMstExtGen(l *gamemap.Level) (*gamemap.Level, []gamemap.Room) {
|
||||
|
||||
rng := util.NewRNG()
|
||||
|
||||
@ -18,9 +17,9 @@ func DelaunayMstExtGen(ctx appctx.ClientCtx, l *gamemap.Level) (*gamemap.Level,
|
||||
l.SetTileByXY(i, j, gamemap.NewWall())
|
||||
}
|
||||
}
|
||||
rooms := GetRandomRoomList(ctx, rng, l, maxrooms, minRoomSize, maxRoomSize)
|
||||
rooms := GetRandomRoomList(rng, l, maxrooms, minRoomSize, maxRoomSize)
|
||||
|
||||
BlitToLevel(ctx, l, rooms)
|
||||
BlitToLevel(l, rooms)
|
||||
|
||||
centers := make([]types.Coords, 0)
|
||||
for _, room := range rooms {
|
||||
|
@ -18,9 +18,9 @@ func DelaunayPureGen(ctx context.Context, l *gamemap.Level) (*gamemap.Level, []g
|
||||
l.SetTileByXY(i, j, gamemap.NewWall())
|
||||
}
|
||||
}
|
||||
rooms := GetRandomRoomList(ctx, rng, l, maxrooms, minRoomSize, maxRoomSize)
|
||||
rooms := GetRandomRoomList(rng, l, maxrooms, minRoomSize, maxRoomSize)
|
||||
|
||||
BlitToLevel(ctx, l, rooms)
|
||||
BlitToLevel(l, rooms)
|
||||
|
||||
centers := make([]types.Coords, 0)
|
||||
for _, room := range rooms {
|
||||
|
@ -1,9 +1,9 @@
|
||||
package gamemap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/items"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/mob"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
@ -30,7 +30,7 @@ type PrefabRecord struct {
|
||||
}
|
||||
|
||||
func LoadPrefabFile(filename string) (*PrefabFile, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
data, err := os.ReadFile(filename)
|
||||
if err!= nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -42,12 +42,10 @@ func LoadPrefabFile(filename string) (*PrefabFile, error) {
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
type PrefabLoader struct {
|
||||
ctx context.Context
|
||||
}
|
||||
type PrefabLoader struct {}
|
||||
|
||||
func NewPrefabLoader(ctx context.Context) PrefabLoader {
|
||||
return PrefabLoader{ctx: ctx}
|
||||
func NewPrefabLoader() PrefabLoader {
|
||||
return PrefabLoader{}
|
||||
}
|
||||
|
||||
func (pfbl PrefabLoader) PrefabRoomsList() []Room {
|
||||
@ -65,6 +63,8 @@ func (pfbl PrefabLoader) PrefabRoomsList() []Room {
|
||||
|
||||
for _, rawPrefab := range file.Prefabs {
|
||||
|
||||
appctx.Logger().Debug().Msgf("adding %s", rawPrefab.name)
|
||||
|
||||
for k,v := range rawPrefab.TileLegend {
|
||||
currentTileLegend[k] = v
|
||||
}
|
||||
@ -76,8 +76,8 @@ func (pfbl PrefabLoader) PrefabRoomsList() []Room {
|
||||
}
|
||||
|
||||
room := Room{
|
||||
Rect:types.Rect{0, 0, rawPrefab.Size.X, rawPrefab.Size.Y},
|
||||
Center: types.Coords{rawPrefab.Size.X / 2, rawPrefab.Size.Y / 2}, //fixme
|
||||
Rect:types.Rect{X: 0, Y: 0, W: rawPrefab.Size.X, H: rawPrefab.Size.Y},
|
||||
Center: types.Coords{X: rawPrefab.Size.X / 2, Y: rawPrefab.Size.Y / 2}, //fixme
|
||||
Geometry: make([]func()*Tile, rawPrefab.Size.X*rawPrefab.Size.Y),
|
||||
Mobs: make([]mob.Mob, rawPrefab.Size.X*rawPrefab.Size.Y),
|
||||
Items: make([]items.Carried, rawPrefab.Size.X*rawPrefab.Size.Y),
|
||||
@ -102,11 +102,11 @@ func (pfbl PrefabLoader) PrefabRoomsList() []Room {
|
||||
}
|
||||
if shortName == "connector" {
|
||||
f = NewWall
|
||||
room.Connectors = append(room.Connectors, types.Coords{i,j})
|
||||
room.Connectors = append(room.Connectors, types.Coords{X: i,Y: j})
|
||||
} else {
|
||||
f, ok = TileTypeMap[shortName]
|
||||
if (!ok) {
|
||||
appctx.Logger(pfbl.ctx).Warn().Msgf("Unknown tile: %s", shortName)
|
||||
appctx.Logger().Warn().Msgf("Unknown tile: %s", shortName)
|
||||
}
|
||||
}
|
||||
room.Geometry[i+ j*room.W] = f
|
||||
|
@ -1,15 +1,15 @@
|
||||
package gamemap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/items"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/mob"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util/appctx"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var invalidBlit = errors.New("trying to blit on existing good tile")
|
||||
@ -28,19 +28,19 @@ func (r *Room) Put (x, y int, tileFunc interface{}) {
|
||||
if tf == nil {
|
||||
return //fixme error
|
||||
}
|
||||
if r.InBounds(types.Coords{x, y}) {
|
||||
if r.InBounds(types.Coords{X: x, Y: y}) {
|
||||
r.Geometry[x+y*r.W] = tf
|
||||
}
|
||||
}
|
||||
|
||||
func (room *Room) BlitToLevel(ctx context.Context, l *Level) error {
|
||||
func (room *Room) BlitToLevel(l *Level) error {
|
||||
//copy tiles like this:
|
||||
//https://stackoverflow.com/questions/21011023/copy-pointer-values-a-b-in-golang
|
||||
|
||||
for j := 0; j < room.H; j++ {
|
||||
|
||||
for i := 0; i < room.W; i++ {
|
||||
mapCoords := types.Coords{room.X + i, room.Y + j}
|
||||
mapCoords := types.Coords{X: room.X + i, Y: room.Y + j}
|
||||
underlyingTile := l.GetTile(mapCoords)
|
||||
|
||||
tileFunc := room.Geometry[i+j*room.W]
|
||||
@ -51,7 +51,7 @@ func (room *Room) BlitToLevel(ctx context.Context, l *Level) error {
|
||||
//check underlying tile
|
||||
if underlyingTile == nil ||
|
||||
underlyingTile.Name != "Wall" {
|
||||
appctx.Logger(ctx).Warn().Msg("Invalid blit!")
|
||||
appctx.Logger().Warn().Msg("Invalid blit!")
|
||||
return invalidBlit
|
||||
}
|
||||
l.Put(mapCoords.X, mapCoords.Y, tileFunc)
|
||||
@ -86,17 +86,17 @@ func NewRandomRectRoom(rng *util.RNG, w, h int, fillage types.RectFill) Room {
|
||||
w,
|
||||
h,
|
||||
),
|
||||
Center: types.Coords{w / 2, h /2 },
|
||||
Center: types.Coords{X: w / 2, Y: h /2 },
|
||||
Geometry: make([]func()*Tile, w*h),
|
||||
}
|
||||
newRoom.Blit(fillage, &newRoom)
|
||||
//add connectors
|
||||
newRoom.Connectors = append(
|
||||
newRoom.Connectors,
|
||||
types.Coords{rng.Range(1, w - 2), 1},
|
||||
types.Coords{rng.Range(1, w - 2), h -2},
|
||||
types.Coords{1, rng.Range(1, h - 2)},
|
||||
types.Coords{w - 2, rng.Range(1, h - 2)},
|
||||
types.Coords{X: rng.Range(1, w - 2), Y: 1},
|
||||
types.Coords{X: rng.Range(1, w - 2), Y: h -2},
|
||||
types.Coords{X: 1, Y: rng.Range(1, h - 2)},
|
||||
types.Coords{X: w - 2, Y: rng.Range(1, h - 2)},
|
||||
)
|
||||
return newRoom
|
||||
}
|
||||
@ -105,7 +105,7 @@ func (r *Room) String() string {
|
||||
return strings.Join([]string{
|
||||
"room: ",
|
||||
"\t" + fmt.Sprintf(" rect: X: %d, Y: %d, maxX: %d, maxY: %d", r.Rect.X, r.Rect.Y, r.Rect.W + r.X - 1, r.Rect.H + r.Y - 1),
|
||||
"\t" + fmt.Sprintf(" center:", r.Center.X, r.Center.Y),
|
||||
"\t" + fmt.Sprintf(" center: %d, %d", r.Center.X, r.Center.Y),
|
||||
"\t" + fmt.Sprintf(" Connectors: %v", r.Connectors),
|
||||
},"\n") + "\n"
|
||||
}
|
||||
}
|
||||
|
@ -37,21 +37,22 @@ func (t *Tile) GetRawBgColor() uint32 {
|
||||
|
||||
func NewWall() *Tile {
|
||||
return &Tile{
|
||||
Appearance: &Appearance{
|
||||
Glyph: &PlainGlyphHolder{Glyph: "#"},
|
||||
ColorSet: TileColorSet{
|
||||
Fg: &PlainColorHolder{A: 255, R: 130, G: 110, B: 150},
|
||||
Bg: &PlainColorHolder{A: 255, R: 172, G: 170, B: 173},
|
||||
DarkFg: &PlainColorHolder{A: 255, R: 20, G: 20, B: 68},
|
||||
DarkBg: &PlainColorHolder{A: 255, R: 7, G: 7, B: 30},
|
||||
},
|
||||
},
|
||||
Name: "Wall",
|
||||
Description: "A dull rock wall",
|
||||
BlocksPass: true,
|
||||
BlocksSight: true,
|
||||
Explored: false,
|
||||
MustDraw: false,
|
||||
Appearance: &Appearance{
|
||||
Glyph: &PlainGlyphHolder{"#"},
|
||||
ColorSet: TileColorSet{
|
||||
Fg: &PlainColorHolder{255, 130, 110, 150},
|
||||
Bg: &PlainColorHolder{255, 172, 170, 173},
|
||||
DarkFg: &PlainColorHolder{255, 20, 20, 68},
|
||||
DarkBg: &PlainColorHolder{255, 7, 7, 30},
|
||||
},
|
||||
},
|
||||
Visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,18 +65,18 @@ func NewDecoratedWall() *Tile {
|
||||
Explored: false,
|
||||
MustDraw: false,
|
||||
Appearance: &Appearance{
|
||||
Glyph: &PlainGlyphHolder{"#"},
|
||||
Glyph: &PlainGlyphHolder{Glyph: "#"},
|
||||
ColorSet: TileColorSet{
|
||||
Fg: &PlainColorHolder{255, 130, 110, 150},
|
||||
Fg: &PlainColorHolder{A: 255, R: 130, G: 110, B: 150},
|
||||
//Bg: &PlainColorHolder{255, 172, 170, 173},
|
||||
Bg: &DanceColorHolder{
|
||||
255,
|
||||
DeviatedColorRing(172, -15, 10),
|
||||
DeviatedColorRing(170, -5, 15),
|
||||
DeviatedColorRing(173, -10, 10),
|
||||
A: 255,
|
||||
R: DeviatedColorRing(172, -15, 10),
|
||||
G: DeviatedColorRing(170, -5, 15),
|
||||
B: DeviatedColorRing(173, -10, 10),
|
||||
},
|
||||
DarkFg: &PlainColorHolder{255, 20, 20, 68},
|
||||
DarkBg: &PlainColorHolder{255, 7, 7, 30},
|
||||
DarkFg: &PlainColorHolder{A: 255, R: 20, G: 20, B: 68},
|
||||
DarkBg: &PlainColorHolder{A: 255, R: 7, G: 7, B: 30},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -90,12 +91,12 @@ func NewFloor() *Tile {
|
||||
Explored: false,
|
||||
MustDraw: false,
|
||||
Appearance: &Appearance{
|
||||
Glyph: &PlainGlyphHolder{"."},
|
||||
Glyph: &PlainGlyphHolder{Glyph: "."},
|
||||
ColorSet: TileColorSet{
|
||||
Fg: &PlainColorHolder{255, 220, 220, 250},
|
||||
Bg: &PlainColorHolder{255, 19, 19, 70},
|
||||
DarkFg: &PlainColorHolder{255, 30, 20, 50},
|
||||
DarkBg: &PlainColorHolder{255, 7, 7, 30},
|
||||
Fg: &PlainColorHolder{A: 255, R: 220, G: 220, B: 250},
|
||||
Bg: &PlainColorHolder{A: 255, R: 19, G: 19, B: 70},
|
||||
DarkFg: &PlainColorHolder{A: 255, R: 30, G: 20, B: 50},
|
||||
DarkBg: &PlainColorHolder{A: 255, R: 7, G: 7, B: 30},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -111,17 +112,17 @@ func NewWaterTile() *Tile {
|
||||
Explored: false,
|
||||
MustDraw: true, //fixme debug
|
||||
Appearance: &Appearance{
|
||||
Glyph: &PlainGlyphHolder{" "},
|
||||
Glyph: &PlainGlyphHolder{Glyph: " "},
|
||||
ColorSet: TileColorSet{
|
||||
Fg: &PlainColorHolder{255, 220, 220, 250},
|
||||
Fg: &PlainColorHolder{A: 255, R: 220, G: 220, B: 250},
|
||||
Bg: &DanceColorHolder{
|
||||
255,
|
||||
SingleColorRing(5),
|
||||
FillColorRing(2, 2, 42, 4),
|
||||
FillColorRing(154, 150, 229, 12),
|
||||
A: 255,
|
||||
R: SingleColorRing(5),
|
||||
G: FillColorRing(2, 2, 42, 4),
|
||||
B: FillColorRing(154, 150, 229, 12),
|
||||
},
|
||||
DarkFg: &PlainColorHolder{255, 30, 20, 50},
|
||||
DarkBg: &PlainColorHolder{255, 7, 7, 30},
|
||||
DarkFg: &PlainColorHolder{A: 255, R: 30, G: 20, B: 50},
|
||||
DarkBg: &PlainColorHolder{A: 255, R: 7, G: 7, B: 30},
|
||||
},
|
||||
},
|
||||
|
||||
@ -131,27 +132,21 @@ func NewWaterTile() *Tile {
|
||||
func NewDeepWaterTile() *Tile {
|
||||
//ch := &ColorHolder{5, 2, 154}
|
||||
return &Tile{
|
||||
Appearance: &Appearance{
|
||||
Glyph: &PlainGlyphHolder{Glyph: " "},
|
||||
ColorSet: TileColorSet{
|
||||
Fg: &PlainColorHolder{A: 255, R: 220, G: 220, B: 250},
|
||||
Bg: &DanceColorHolder{A: 255, R: SingleColorRing(19), G: FillColorRing(19, 0, 15, 2), B: FillColorRing(127, 120, 176, 12)},
|
||||
DarkFg: &PlainColorHolder{A: 255, R: 30, G: 20, B: 50},
|
||||
DarkBg: &PlainColorHolder{A: 255, R: 7, G: 7, B: 30},
|
||||
},
|
||||
},
|
||||
Name: "Deep Water",
|
||||
Description: "Deep water",
|
||||
BlocksPass: false,
|
||||
BlocksSight: false,
|
||||
Explored: false,
|
||||
MustDraw: true, //fixme debug
|
||||
|
||||
Appearance: &Appearance{
|
||||
Glyph: &PlainGlyphHolder{" "},
|
||||
ColorSet: TileColorSet{
|
||||
Fg: &PlainColorHolder{255, 220, 220, 250},
|
||||
Bg: &DanceColorHolder{
|
||||
255,
|
||||
SingleColorRing(19),
|
||||
FillColorRing(19, 0, 15, 2),
|
||||
FillColorRing(127, 120, 176, 12),
|
||||
},
|
||||
DarkFg: &PlainColorHolder{255, 30, 20, 50},
|
||||
DarkBg: &PlainColorHolder{255, 7, 7, 30},
|
||||
},
|
||||
},
|
||||
|
||||
MustDraw: true,
|
||||
Visible: false,
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,13 @@
|
||||
package items
|
||||
|
||||
import "lab.zaar.be/thefish/alchemyst-go/engine/ecs"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/ecs"
|
||||
)
|
||||
var (
|
||||
ErrorInvTooHeavy = fmt.Errorf("too heavy")
|
||||
ErrorInvTooBulky = fmt.Errorf("too bulky")
|
||||
)
|
||||
type Backpack struct {
|
||||
MaxNumber int
|
||||
MaxBulk int
|
||||
@ -13,7 +19,7 @@ func (b Backpack) Type() string {
|
||||
return ecs.BackpackComponent
|
||||
}
|
||||
|
||||
func (b *Backpack) HasFreeSpace(Bulk, Mass int) bool {
|
||||
func (b *Backpack) HasFreeSpace(Bulk, Mass int) error {
|
||||
totalBulk, totalMass := 0, 0
|
||||
for i, _ := range b.items {
|
||||
tmp := Controller.GetComponent(b.items[i], Carried{}.Type()).(Carried)
|
||||
@ -22,14 +28,12 @@ func (b *Backpack) HasFreeSpace(Bulk, Mass int) bool {
|
||||
totalMass += carried.Mass
|
||||
}
|
||||
if totalMass >= b.MaxMass {
|
||||
//fixme return message along - 'too heavy'
|
||||
return false
|
||||
return ErrorInvTooHeavy
|
||||
}
|
||||
if totalBulk >= b.MaxMass {
|
||||
//fixme return message along - 'doesnt fit to your backpack'
|
||||
return false
|
||||
if totalBulk >= b.MaxBulk {
|
||||
return ErrorInvTooBulky
|
||||
}
|
||||
return true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Backpack) GetItems() []ecs.Entity {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package items
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/ecs"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
)
|
||||
@ -19,32 +20,33 @@ func (c Carried) Type() string {
|
||||
return ecs.CarriedComponent
|
||||
}
|
||||
|
||||
func (c Carried) Pickup(who, what ecs.Entity) {
|
||||
func (c Carried) Pickup(who, what ecs.Entity) error {
|
||||
// check if im lying on ground
|
||||
if !Controller.HasComponent(what, ecs.CoordsComponent) {
|
||||
return
|
||||
return fmt.Errorf("bug! item with no coords?!")
|
||||
}
|
||||
// something inexistent on map trying to pickup an item?!
|
||||
if !Controller.HasComponent(who, ecs.CoordsComponent) {
|
||||
//todo log error - investigate this situation
|
||||
return
|
||||
return fmt.Errorf("bug! actor with no coords?!")
|
||||
}
|
||||
//check if who and what are on the same tile
|
||||
//check if who and what are in adjacent tiles
|
||||
whoCoords := Controller.GetComponent(who, ecs.CoordsComponent).(types.Coords)
|
||||
whatCoords := Controller.GetComponent(what, ecs.CoordsComponent).(types.Coords)
|
||||
if whoCoords != whatCoords {
|
||||
if !whoCoords.IsAdjacentTo(&whatCoords) {
|
||||
//todo log error - something strange happened
|
||||
return
|
||||
return fmt.Errorf("bug! actor and item in inadjacent coords?!")
|
||||
}
|
||||
//does not have inventory?
|
||||
if !Controller.HasComponent(who, ecs.BackpackComponent) {
|
||||
//todo send message - you cant carry items
|
||||
return
|
||||
return fmt.Errorf("bug! actor cannot carry items")
|
||||
}
|
||||
bp := Controller.GetComponent(who, Backpack{}.Type()).(Backpack)
|
||||
if !bp.HasFreeSpace(c.Bulk, c.Mass) {
|
||||
err := bp.HasFreeSpace(c.Bulk, c.Mass)
|
||||
if err != nil {
|
||||
//todo send message - does not fit to your inventory
|
||||
return
|
||||
return err
|
||||
}
|
||||
//do not remove appearance
|
||||
//remove coords instead (does not exist on map anymore)
|
||||
@ -52,6 +54,7 @@ func (c Carried) Pickup(who, what ecs.Entity) {
|
||||
bp.items = append(bp.items, what)
|
||||
//fuck that, we need to update constantly
|
||||
Controller.UpdateComponent(who, ecs.BackpackComponent, bp)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Carried) Drop(who, what ecs.Entity) {
|
||||
@ -90,13 +93,14 @@ func (c *Carried) GetBulk(what ecs.Entity) int {
|
||||
}
|
||||
|
||||
func FindCarriedUnder(who ecs.Entity) []ecs.Entity {
|
||||
coords := Controller.GetComponent(who, ecs.CoordsComponent).(types.Coords)
|
||||
carrieds := Controller.GetEntitiesWithComponent(ecs.CarriedComponent)
|
||||
pickerCoords := Controller.GetComponent(who, ecs.CoordsComponent).(types.Coords)
|
||||
// _И_ носимые _И_ имеющие координаты, т.е. где-то лежащие
|
||||
carrieds := Controller.GetEntitiesWithComponent(ecs.CarriedComponent, ecs.CoordsComponent)
|
||||
result := make([]ecs.Entity, 0)
|
||||
for _, ent := range carrieds {
|
||||
car := Controller.GetComponent(ent, ecs.CoordsComponent)
|
||||
if car == coords {
|
||||
result = append(result, ent)
|
||||
for _, carried := range carrieds {
|
||||
carriedCoords := Controller.GetComponent(carried, ecs.CoordsComponent).(types.Coords)
|
||||
if pickerCoords.IsAdjacentTo(&carriedCoords) {
|
||||
result = append(result, carried)
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
@ -1,5 +1,7 @@
|
||||
package itemprops
|
||||
|
||||
import "github.com/shopspring/decimal"
|
||||
|
||||
//MedicalSystem организм
|
||||
// Humanoid
|
||||
// Circuits
|
||||
@ -55,94 +57,95 @@ package itemprops
|
||||
// -> [right] joint -> leg -> joint -> hip -> joint -> foot -> 5 x finger
|
||||
// -> head -> joint -> jaw
|
||||
|
||||
//MedicalSystem Организм
|
||||
// MedicalSystem Организм
|
||||
type MedicalSystem struct {
|
||||
BasePart BodyPart
|
||||
BasePart BodyPart
|
||||
}
|
||||
|
||||
//MedicalCircuit Система обращения
|
||||
// MedicalCircuit Система обращения
|
||||
type MedicalCircuit struct {
|
||||
Provides MedicalAbility
|
||||
DependsOn Organ
|
||||
Vessel MedicalVessel
|
||||
Contains []Organ
|
||||
Provides MedicalAbility
|
||||
DependsOn Organ
|
||||
Vessel MedicalVessel
|
||||
Contains []Organ
|
||||
}
|
||||
|
||||
//MedicalVessel кровь, желчь, пульпа, воздух, еда
|
||||
// MedicalVessel кровь, желчь, пульпа, воздух, еда
|
||||
type MedicalVessel struct {
|
||||
Name string
|
||||
Material
|
||||
Pressure DimensionItemDensity
|
||||
Name string
|
||||
Material
|
||||
Pressure decimal.Decimal //Pressure давление, kg / m3
|
||||
}
|
||||
|
||||
//BodyPart часть тела
|
||||
// BodyPart часть тела
|
||||
type BodyPart struct {
|
||||
LayerExtra MedicalMaterial
|
||||
LayerOuter MedicalMaterial
|
||||
LayerMiddle MedicalMaterial
|
||||
LayerInner MedicalMaterial
|
||||
LayerExtra MedicalMaterial
|
||||
LayerOuter MedicalMaterial
|
||||
LayerMiddle MedicalMaterial
|
||||
LayerInner MedicalMaterial
|
||||
|
||||
Joints []Joint
|
||||
Contains []InnerOrgan
|
||||
Exposes []OuterOrgan
|
||||
Size DimensionItemSize
|
||||
Joints []Joint
|
||||
Contains []InnerOrgan
|
||||
Exposes []OuterOrgan
|
||||
}
|
||||
|
||||
//Joint суставы, к чему и что крепится
|
||||
// Joint суставы, к чему и что крепится
|
||||
type Joint struct {
|
||||
Name string
|
||||
ConnectsFrom BodyPart
|
||||
ConnectsTo BodyPart
|
||||
Name string
|
||||
ConnectsFrom BodyPart
|
||||
ConnectsTo BodyPart
|
||||
}
|
||||
|
||||
type Organ struct {
|
||||
Name string
|
||||
Material
|
||||
Name string
|
||||
Material
|
||||
}
|
||||
|
||||
//InnerOrgan ливер, селезёнка, сердце, кишки итп
|
||||
// InnerOrgan ливер, селезёнка, сердце, кишки итп
|
||||
type InnerOrgan struct {
|
||||
Organ
|
||||
DependsOn MedicalCircuit
|
||||
BelongsTo MedicalCircuit
|
||||
Organ
|
||||
DependsOn MedicalCircuit
|
||||
BelongsTo MedicalCircuit
|
||||
}
|
||||
|
||||
//OuterOrgan глаза, уши, волосы, когти итп
|
||||
// OuterOrgan глаза, уши, волосы, когти итп
|
||||
type OuterOrgan struct {
|
||||
Organ
|
||||
DependsOn MedicalCircuit
|
||||
BelongsTo MedicalCircuit
|
||||
Organ
|
||||
DependsOn MedicalCircuit
|
||||
BelongsTo MedicalCircuit
|
||||
}
|
||||
|
||||
//слой части тела - кожа/чешуя/роговые пластины/хитиновый панцирь, жир, мускулы, кости
|
||||
// слой части тела - кожа/чешуя/роговые пластины/хитиновый панцирь, жир, мускулы, кости
|
||||
type MedicalMaterial struct {
|
||||
Name string
|
||||
Material
|
||||
MedicalSystemFlags
|
||||
Name string
|
||||
Material
|
||||
MedicalSystemFlags
|
||||
}
|
||||
|
||||
//@todo заменить на Medical Circuit
|
||||
// @todo заменить на Medical Circuit
|
||||
type MedicalSystemFlags struct {
|
||||
//Structural является ли опорным аппаратом
|
||||
Structural bool
|
||||
//Содежит ли кровь/ихор/
|
||||
MajorVeins bool //вход на мотор, сломаешь - быстро выйдет из строя если будет двигаться
|
||||
MajorArteria bool //выход, то же самое + высокое давление
|
||||
//Structural является ли опорным аппаратом
|
||||
Structural bool
|
||||
//Содежит ли кровь/ихор/
|
||||
MajorVeins bool //вход на мотор, сломаешь - быстро выйдет из строя если будет двигаться
|
||||
MajorArteria bool //выход, то же самое + высокое давление
|
||||
|
||||
Veins bool //вход на мотор
|
||||
Arteria bool //выход из мотора, высокое давление
|
||||
Veins bool //вход на мотор
|
||||
Arteria bool //выход из мотора, высокое давление
|
||||
|
||||
MajorNerve bool //повредишь - ниже по суставам не работает
|
||||
NerveTissue bool //повредишь - ниже по суставамс болит
|
||||
MajorNerve bool //повредишь - ниже по суставам не работает
|
||||
NerveTissue bool //повредишь - ниже по суставамс болит
|
||||
|
||||
OxygenTube bool //трахея
|
||||
OxygenPump bool //лёгкое
|
||||
OxygenTube bool //трахея
|
||||
OxygenPump bool //лёгкое
|
||||
|
||||
BloodPump bool //мотор
|
||||
BloodPump bool //мотор
|
||||
|
||||
ContainsCongestionLiquid bool
|
||||
ContainsCongestionLiquid bool
|
||||
|
||||
IsMainCongestionPump bool
|
||||
IsMainCongestionPump bool
|
||||
}
|
||||
|
||||
//MedicalAbility спсобность есть, стоять, не терять равновесие, дышать, выздоравливать, лечить свои органы, видеть итп
|
||||
// MedicalAbility спсобность есть, стоять, не терять равновесие, дышать, выздоравливать, лечить свои органы, видеть итп
|
||||
type MedicalAbility string
|
||||
|
215
engine/items/itemprops/ip_test.go
Normal file
215
engine/items/itemprops/ip_test.go
Normal file
@ -0,0 +1,215 @@
|
||||
package itemprops
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type IpTestSuite struct {
|
||||
suite.Suite
|
||||
}
|
||||
|
||||
// метод для установки тестовых данных для всего набора, запускается до всего прочего
|
||||
func (suite *IpTestSuite) SetupSuite() {
|
||||
}
|
||||
|
||||
// метод для сноса наделанного тестом до идеально чистого состояния, запускается после всего прочего
|
||||
func (suite *IpTestSuite) TearDownSuite() {
|
||||
}
|
||||
|
||||
// запускается перед каждым тестом
|
||||
func (suite *IpTestSuite) SetupTest() {}
|
||||
|
||||
// запускается после каждого теста
|
||||
func (suite *IpTestSuite) TearDownTest() {}
|
||||
|
||||
func TestApi(t *testing.T) {
|
||||
tests := new(IpTestSuite)
|
||||
suite.Run(t, tests)
|
||||
}
|
||||
|
||||
func (suite *IpTestSuite) TestMaterialWeightAndVolume() {
|
||||
// плотность https://tekkos.ru/katalog/poleznaya-informatsiya/tablica-plotnosti-stali-kg-m3.html
|
||||
// ударная вязкость https://nposanef.ru/DOCUMENTS/PB-03-605-03/PB-03-605-03_Tab-2.6.pdf
|
||||
// температура плавления http://zaozmi.ru/polezno/temperatura_plavleniya_metallov.html
|
||||
// температура кипения http://temperatures.ru/pages/temperatura_plavleniya_i_kipeniya
|
||||
// Пределы прочности некоторых материалов https://sevparitet.ru/raznoe/koefficient-uprugosti-tablica.html
|
||||
|
||||
metalMaterialFlags := MaterialFlags{
|
||||
ConductsElictricity: true,
|
||||
BlocksLiquid: true,
|
||||
AcidResistant: true,
|
||||
BlocksGas: true,
|
||||
Flammable: false,
|
||||
ConductsHeat: true,
|
||||
Radiates: true,
|
||||
}
|
||||
|
||||
woodMaterialFlags := MaterialFlags{
|
||||
ConductsElictricity: false,
|
||||
BlocksLiquid: true,
|
||||
AcidResistant: false,
|
||||
BlocksGas: true,
|
||||
Flammable: true,
|
||||
ConductsHeat: false,
|
||||
Radiates: false,
|
||||
}
|
||||
|
||||
teststeel := Material{
|
||||
Id: "teststeel",
|
||||
Name: "steel",
|
||||
Flags: metalMaterialFlags,
|
||||
Density: DimensionItemDensity{decimal.NewFromInt(7800)},
|
||||
FractureToughness: DimensionFractureToughness{decimal.NewFromInt(30)},
|
||||
MeltingPoint: DimensionItemNullTemperature{decimal.NullDecimal{Decimal: decimal.NewFromInt(1400), Valid: true}},
|
||||
BoilingPoint: DimensionItemNullTemperature{decimal.NullDecimal{Decimal: decimal.NewFromInt(3200), Valid: true}},
|
||||
}
|
||||
|
||||
testOakWood := Material{
|
||||
Id: "testoakwood",
|
||||
Name: "oakwood",
|
||||
Flags: woodMaterialFlags,
|
||||
Density: DimensionItemDensity{decimal.NewFromInt(700)},
|
||||
FractureToughness: DimensionFractureToughness{decimal.NewFromFloat(4.5)},
|
||||
MeltingPoint: DimensionItemNullTemperature{decimal.NullDecimal{Decimal: decimal.NewFromInt(600), Valid: true}}, //загорается при 600 град Цельсия
|
||||
}
|
||||
|
||||
testCube := ItemPhysics{
|
||||
Material: teststeel,
|
||||
DimensionItemRigidity: DimensionItemRigidity{decimal.NewFromInt(65)},
|
||||
DimensionItemSize: DimensionItemSize{
|
||||
Width: decimal.NewFromFloat(0.1),
|
||||
Height: decimal.NewFromFloat(0.1),
|
||||
Depth: decimal.NewNullDecimal(decimal.NewFromFloat(0.1)),
|
||||
Thickness: decimal.NullDecimal{},
|
||||
},
|
||||
DimensionItemTemperature: DimensionItemTemperature{decimal.NewFromInt(20)},
|
||||
}
|
||||
|
||||
suite.Equal(decimal.NewFromFloat(7.8).String(), testCube.Weight().String())
|
||||
suite.Equal(decimal.NewFromFloat(0.001).String(), testCube.Volume().String())
|
||||
|
||||
testOakCube := ItemPhysics{
|
||||
Material: testOakWood,
|
||||
DimensionItemRigidity: DimensionItemRigidity{decimal.NewFromInt(4)},
|
||||
DimensionItemSize: DimensionItemSize{
|
||||
Width: decimal.NewFromFloat(0.1),
|
||||
Height: decimal.NewFromFloat(0.1),
|
||||
Depth: decimal.NewNullDecimal(decimal.NewFromFloat(0.1)),
|
||||
Thickness: decimal.NullDecimal{},
|
||||
},
|
||||
DimensionItemTemperature: DimensionItemTemperature{decimal.NewFromInt(20)},
|
||||
}
|
||||
|
||||
//oakwood is ~10 times lighter than steel
|
||||
suite.Equal(decimal.NewFromFloat(0.7).String(), testOakCube.Weight().String())
|
||||
suite.Equal(decimal.NewFromFloat(0.001).String(), testOakCube.Volume().String())
|
||||
|
||||
testCuirass := ItemPhysics{
|
||||
Material: teststeel,
|
||||
DimensionItemRigidity: DimensionItemRigidity{decimal.NewFromInt(55)},
|
||||
DimensionItemSize: DimensionItemSize{
|
||||
Width: decimal.NewFromFloat(0.5), //60 cm wide
|
||||
Height: decimal.NewFromFloat(0.8), //80 cm high
|
||||
Depth: decimal.NewNullDecimal(decimal.NewFromFloat(0.4)), //50 cm deep
|
||||
Thickness: decimal.NewNullDecimal(decimal.NewFromFloat(0.001)), // 1mm thick
|
||||
},
|
||||
DimensionItemTemperature: DimensionItemTemperature{},
|
||||
}
|
||||
|
||||
//12.1992 kg HEAVY ARMOR IS HEAVY
|
||||
suite.Equal(decimal.NewFromFloat(12.1992).String(), testCuirass.Weight().String())
|
||||
//0.001564 m3 of steel
|
||||
suite.Equal(decimal.NewFromFloat(0.001564).String(), testCuirass.Volume().String())
|
||||
}
|
||||
|
||||
func (suite *IpTestSuite) TestMaterialSerialization() {
|
||||
metalMaterialFlags := MaterialFlags{
|
||||
ConductsElictricity: true,
|
||||
BlocksLiquid: true,
|
||||
AcidResistant: true,
|
||||
BlocksGas: true,
|
||||
Flammable: false,
|
||||
ConductsHeat: true,
|
||||
Radiates: true,
|
||||
}
|
||||
|
||||
woodMaterialFlags := MaterialFlags{
|
||||
ConductsElictricity: false,
|
||||
BlocksLiquid: true,
|
||||
AcidResistant: false,
|
||||
BlocksGas: true,
|
||||
Flammable: true,
|
||||
ConductsHeat: false,
|
||||
Radiates: false,
|
||||
}
|
||||
|
||||
teststeel := Material{
|
||||
Id: "teststeel",
|
||||
Name: "steel",
|
||||
Flags: metalMaterialFlags,
|
||||
Density: DimensionItemDensity{decimal.NewFromInt(7800)},
|
||||
FractureToughness: DimensionFractureToughness{decimal.NewFromInt(30)},
|
||||
MeltingPoint: DimensionItemNullTemperature{decimal.NullDecimal{Decimal: decimal.NewFromInt(1400), Valid: true}},
|
||||
BoilingPoint: DimensionItemNullTemperature{decimal.NullDecimal{Decimal: decimal.NewFromInt(3200), Valid: true}},
|
||||
}
|
||||
|
||||
testOakWood := Material{
|
||||
Id: "testoakwood",
|
||||
Name: "oakwood",
|
||||
Flags: woodMaterialFlags,
|
||||
Density: DimensionItemDensity{decimal.NewFromInt(700)},
|
||||
FractureToughness: DimensionFractureToughness{decimal.NewFromFloat(4.5)},
|
||||
MeltingPoint: DimensionItemNullTemperature{decimal.NullDecimal{Decimal: decimal.NewFromInt(600), Valid: true}}, //загорается при 600 град Цельсия
|
||||
}
|
||||
bytes, err := json.Marshal(teststeel)
|
||||
suite.NoError(err)
|
||||
suite.Equal(
|
||||
`{"id":"teststeel","name":"steel","material_flags":{"conducts_elictricity":true,"blocks_liquid":true,"acid_resistant":true,"blocks_gas":true,"flammable":false,"conducts_heat":true,"radiates":true},"density":"7800","fracture_toughness":"30","melting_point":"1400","boiling_point":"3200"}`,
|
||||
string(bytes),
|
||||
)
|
||||
|
||||
bytes, err = json.Marshal(testOakWood)
|
||||
suite.NoError(err)
|
||||
suite.Equal(`{"id":"testoakwood","name":"oakwood","material_flags":{"conducts_elictricity":false,"blocks_liquid":true,"acid_resistant":false,"blocks_gas":true,"flammable":true,"conducts_heat":false,"radiates":false},"density":"700","fracture_toughness":"4.5","melting_point":"600","boiling_point":null}`,
|
||||
string(bytes),
|
||||
)
|
||||
}
|
||||
|
||||
func (suite *IpTestSuite) TestMaterialDeserialization() {
|
||||
|
||||
logger := log.Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339})
|
||||
mm, err := NewMaterialMap("../../..", logger)
|
||||
suite.NoError(err)
|
||||
|
||||
metalMaterialFlags := MaterialFlags{
|
||||
ConductsElictricity: true,
|
||||
BlocksLiquid: true,
|
||||
AcidResistant: true,
|
||||
BlocksGas: true,
|
||||
Flammable: false,
|
||||
ConductsHeat: true,
|
||||
Radiates: true,
|
||||
}
|
||||
|
||||
teststeel := Material{
|
||||
Id: "steel",
|
||||
Name: "steel",
|
||||
Flags: metalMaterialFlags,
|
||||
Density: DimensionItemDensity{decimal.NewFromInt(7800)},
|
||||
FractureToughness: DimensionFractureToughness{decimal.NewFromInt(30)},
|
||||
MeltingPoint: DimensionItemNullTemperature{decimal.NullDecimal{Decimal: decimal.NewFromInt(1400), Valid: true}},
|
||||
BoilingPoint: DimensionItemNullTemperature{decimal.NullDecimal{Decimal: decimal.NewFromInt(3200), Valid: true}},
|
||||
}
|
||||
|
||||
loadedsteel := mm["steel"]
|
||||
suite.Equalf(teststeel, *loadedsteel, "error: %s")
|
||||
_ = mm
|
||||
}
|
@ -3,35 +3,54 @@ package itemprops
|
||||
import "github.com/shopspring/decimal"
|
||||
|
||||
type ItemPhysics struct {
|
||||
Material Material
|
||||
Rigidity DimensionItemRigidity
|
||||
Size DimensionItemSize
|
||||
Temperature DimensionItemTemperature
|
||||
Material `json:"material"`
|
||||
//DimensionItemRigidity жёсткость (самого предмета а не материала), мера прочности,
|
||||
//уменьшается со временем, может зависеть от тепмературы
|
||||
DimensionItemRigidity `json:"rigidity"`
|
||||
DimensionItemSize `json:"size"`
|
||||
DimensionItemTemperature `json:"temperature"`
|
||||
//CubeFactor фактор близости куб <-> сфера по площади поверхности, чем ближе к 0, тем ближе к сфере,
|
||||
//100 - куб, >100 - для суперсложных (шипастых например) поверхностей
|
||||
CubeFactor decimal.NullDecimal `json:"cube_factor,omitempty"`
|
||||
}
|
||||
|
||||
//DimensionItemSize length in m (1 mm = 1/1000 of 1m)
|
||||
// DimensionItemSize length in m (1 mm = 1/1000 of 1m)
|
||||
type DimensionItemSize struct {
|
||||
Width decimal.Decimal
|
||||
Height decimal.Decimal
|
||||
// if item is solid - depth in m (1mm = 1/1000 of m)
|
||||
Depth decimal.NullDecimal
|
||||
//Thickness if item is hollow - thickness of outer item shell, ie for armor, in m (1 mm = 1/1000 of m)
|
||||
Thickness decimal.NullDecimal
|
||||
Width decimal.Decimal `json:"width"`
|
||||
Height decimal.Decimal `json:"height"`
|
||||
// if item is solid - depth in m (1mm = 1/1000 of m)
|
||||
Depth decimal.NullDecimal `json:"depth,omitempty"`
|
||||
//Thickness if item is hollow - thickness of outer item shell, ie for armor, in m (1 mm = 1/1000 of m)
|
||||
Thickness decimal.NullDecimal `json:"thickness,omitempty"`
|
||||
}
|
||||
|
||||
//Area is frontal area
|
||||
// Area is frontal area
|
||||
func (d *DimensionItemSize) Area() decimal.Decimal {
|
||||
return d.Width.Mul(d.Height)
|
||||
return d.Width.Mul(d.Height)
|
||||
}
|
||||
|
||||
//DimensionItemDensity density in kg/m3
|
||||
type DimensionItemDensity decimal.Decimal
|
||||
func (ip *ItemPhysics) Weight() decimal.Decimal {
|
||||
return ip.Material.Density.Mul(ip.Volume())
|
||||
}
|
||||
|
||||
//DimensionItemRigidity rigidity жёсткость, способность твёрдого тела, конструкции или её элементов сопротивляться деформации in N/m
|
||||
type DimensionItemRigidity decimal.Decimal
|
||||
|
||||
//NotchFractureToughness ударная вязкость по Шарпи, Дж (надо ли?)
|
||||
type NotchFractureToughness decimal.Decimal
|
||||
|
||||
//DimensionItemTemperature in celsius, -273 to 10000
|
||||
type DimensionItemTemperature decimal.Decimal
|
||||
func (d *ItemPhysics) Volume() decimal.Decimal {
|
||||
v := d.Width.Mul(d.Height)
|
||||
//есть глубина
|
||||
if !d.Depth.Decimal.IsZero() {
|
||||
v = v.Mul(d.Depth.Decimal)
|
||||
//пустотелый
|
||||
if !d.Thickness.Decimal.IsZero() {
|
||||
surfaceArea := d.Width.Mul(d.Height).Mul(decimal.NewFromInt(2)).
|
||||
Add(d.Height.Mul(d.Depth.Decimal).Mul(decimal.NewFromInt(2))).
|
||||
Add(d.Width.Mul(d.Depth.Decimal).Mul(decimal.NewFromInt(2)))
|
||||
surfaceAreaCoeff := 0.85
|
||||
if !d.CubeFactor.Decimal.IsZero() {
|
||||
surfaceAreaCoeff = surfaceAreaCoeff * float64(int(d.CubeFactor.Decimal.IntPart())/100)
|
||||
}
|
||||
v = surfaceArea.Mul(d.Thickness.Decimal).
|
||||
//волюнтаристский коэффт отличия поверхности от куба, см https/en.wikipedia.org/wiki/Volume-to-surface_area_ratio
|
||||
Mul(decimal.NewFromFloat(surfaceAreaCoeff))
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
35
engine/items/itemprops/item_physics_dimensions.go
Normal file
35
engine/items/itemprops/item_physics_dimensions.go
Normal file
@ -0,0 +1,35 @@
|
||||
package itemprops
|
||||
|
||||
import "github.com/shopspring/decimal"
|
||||
|
||||
// плотность https://tekkos.ru/katalog/poleznaya-informatsiya/tablica-plotnosti-stali-kg-m3.html
|
||||
// ударная вязкость https://nposanef.ru/DOCUMENTS/PB-03-605-03/PB-03-605-03_Tab-2.6.pdf
|
||||
// температура плавления http://zaozmi.ru/polezno/temperatura_plavleniya_metallov.html
|
||||
// температура кипения http://temperatures.ru/pages/temperatura_plavleniya_i_kipeniya
|
||||
// Пределы прочности некоторых материалов https://sevparitet.ru/raznoe/koefficient-uprugosti-tablica.html
|
||||
|
||||
// DimensionItemDensity density in kg/m3
|
||||
type DimensionItemDensity struct {
|
||||
decimal.Decimal
|
||||
}
|
||||
|
||||
// DimensionItemRigidity rigidity жёсткость, способность твёрдого тела, конструкции или её элементов сопротивляться деформации in N/m
|
||||
type DimensionItemRigidity struct {
|
||||
decimal.Decimal
|
||||
}
|
||||
|
||||
// DimensionFractureToughness ударная вязкость по Шарпи, Дж (надо ли?)
|
||||
// Ударная вязкость, мера скорости поглощения энергии без деформаций, джоули на квадратный метр в секунду
|
||||
type DimensionFractureToughness struct {
|
||||
decimal.Decimal
|
||||
}
|
||||
|
||||
// DimensionItemTemperature in celsius, -273 to 10000
|
||||
type DimensionItemTemperature struct {
|
||||
decimal.Decimal
|
||||
}
|
||||
|
||||
// DimensionItemTemperature in celsius, -273 to 10000
|
||||
type DimensionItemNullTemperature struct {
|
||||
decimal.NullDecimal
|
||||
}
|
@ -1,20 +1,117 @@
|
||||
package itemprops
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Material struct {
|
||||
Name string
|
||||
Flags MaterialFlags
|
||||
Density DimensionItemDensity
|
||||
FractureToughness NotchFractureToughness
|
||||
MeltingPoint DimensionItemTemperature
|
||||
BoilingPoint DimensionItemTemperature
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Flags MaterialFlags `json:"material_flags"`
|
||||
Density DimensionItemDensity `json:"density"`
|
||||
FractureToughness DimensionFractureToughness `json:"fracture_toughness"`
|
||||
MeltingPoint DimensionItemNullTemperature `json:"melting_point,omitempty"`
|
||||
BoilingPoint DimensionItemNullTemperature `json:"boiling_point,omitempty"`
|
||||
}
|
||||
|
||||
func (m Material) Unmarshal() {}
|
||||
|
||||
type MaterialFlags struct {
|
||||
ConductsElictricity bool
|
||||
BlocksLiquid bool
|
||||
AcidResistant bool
|
||||
BlocksGas bool
|
||||
Flammable bool
|
||||
ConductsHeat bool
|
||||
Radiates bool
|
||||
ConductsElictricity bool `json:"conducts_elictricity"`
|
||||
BlocksLiquid bool `json:"blocks_liquid"`
|
||||
AcidResistant bool `json:"acid_resistant"`
|
||||
BlocksGas bool `json:"blocks_gas"`
|
||||
Flammable bool `json:"flammable"`
|
||||
ConductsHeat bool `json:"conducts_heat"`
|
||||
Radiates bool `json:"radiates"`
|
||||
}
|
||||
|
||||
type MaterialMap map[string]*Material
|
||||
|
||||
type tt map[string]MaterialMap
|
||||
|
||||
func NewMaterialMap(path string, logger zerolog.Logger) (MaterialMap, error) {
|
||||
|
||||
mm := make(MaterialMap)
|
||||
tmp := make(map[string][]interface{})
|
||||
flags := make(map[string]*MaterialFlags)
|
||||
err := filepath.Walk(path+"/assets/materials",
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strings.HasSuffix(path, ".json") {
|
||||
splt := strings.Split(path, "/")
|
||||
logger.Info().Msgf("loading %s %d", splt[len(splt)-1], info.Size())
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ttmp := make(map[string][]interface{})
|
||||
err = json.Unmarshal(bytes, &ttmp)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for idx := range ttmp {
|
||||
tmp[idx] = append(tmp[idx], ttmp[idx]...)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = flags
|
||||
if lst, ok := tmp["material_flags"]; ok {
|
||||
for _, item := range lst {
|
||||
ttt := item.(map[string]interface{})
|
||||
_ = ttt
|
||||
for clause, item2 := range ttt {
|
||||
bts, err := json.Marshal(item2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not marshal back:%w", err)
|
||||
}
|
||||
flags[clause] = &MaterialFlags{}
|
||||
err = json.Unmarshal(bts, flags[clause])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not unmarshal to material_flags:%w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Info().Msgf("loaded %d material flag sets", len(flags))
|
||||
|
||||
if lst, ok := tmp["materials"]; ok {
|
||||
for _, item := range lst {
|
||||
ttt := item.(map[string]interface{})
|
||||
_ = ttt
|
||||
for clause, item2 := range ttt {
|
||||
|
||||
toReplace := item2.(map[string]interface{})["material_flags"]
|
||||
|
||||
//todo generalize
|
||||
if ref, ok := toReplace.(map[string]interface{})["$ref"]; ok {
|
||||
rfs := strings.Split(ref.(string), "/")
|
||||
referredFlag := rfs[len(rfs)-1]
|
||||
item2.(map[string]interface{})["material_flags"] = flags[referredFlag]
|
||||
}
|
||||
bts, err := json.Marshal(item2)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not marshal back:%w", err)
|
||||
}
|
||||
mm[clause] = &Material{Id: clause}
|
||||
err = json.Unmarshal(bts, mm[clause])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not unmarshal to material_flags:%w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return mm, err
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ func (ms MatterState) Change(from MatterState, to MatterState) bool {
|
||||
return false
|
||||
}(transitions[from], to)
|
||||
if !newStateFound {
|
||||
log.Warn().Msgf("Transition %s -> %s is impossible", from, to)
|
||||
log.Warn().Msgf(`Transition %d -> %d is impossible`, from, to)
|
||||
return false
|
||||
}
|
||||
// check temperatures/conditions, see template
|
||||
@ -79,4 +79,4 @@ func (ms MatterState) Change(from MatterState, to MatterState) bool {
|
||||
|
||||
return true
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -23,11 +23,11 @@ func (mov Moveable) Walk() {
|
||||
|
||||
//fixme change it to WhatsOnTile
|
||||
func (mov Moveable) IsBlocked(c types.Coords) bool {
|
||||
if mov.Level.GetTile(c).BlocksPass == true {
|
||||
if mov.Level.GetTile(c).BlocksPass {
|
||||
return true
|
||||
}
|
||||
list := mov.Controller.GetEntitiesWithComponent(ecs.MobComponent)
|
||||
for idx, _ := range list {
|
||||
for idx := range list {
|
||||
coords := mov.Controller.GetComponent(list[idx], ecs.CoordsComponent)
|
||||
if coords == nil {
|
||||
continue
|
||||
@ -50,7 +50,7 @@ func (mov Moveable) IsBlocked(c types.Coords) bool {
|
||||
func Walk(entity ecs.Entity, state *gamestate.GameState, dx, dy int) {
|
||||
controller := state.Controller
|
||||
coords := controller.GetComponent(state.Player, ecs.CoordsComponent).(types.Coords)
|
||||
newCoords := types.Coords{coords.X + dx, coords.Y + dy}
|
||||
newCoords := types.Coords{X: coords.X + dx, Y: coords.Y + dy}
|
||||
if !state.Level.InBounds(newCoords) {
|
||||
return
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
package screens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"lab.zaar.be/thefish/alchemyst-go/effects"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/ecs"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/gamestate"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
"lab.zaar.be/thefish/alchemyst-go/ui/mainwindow"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util/appctx"
|
||||
"strings"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"lab.zaar.be/thefish/alchemyst-go/effects"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/ecs"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/gamestate"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
"lab.zaar.be/thefish/alchemyst-go/ui/mainwindow"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util/appctx"
|
||||
)
|
||||
|
||||
type DevmenuScreen struct {
|
||||
@ -69,7 +70,7 @@ func (devm *DevmenuScreen) HandleInput(input string) {
|
||||
level.Tiles[idx].Visible = true
|
||||
level.Tiles[idx].Explored = true
|
||||
}
|
||||
appctx.Logger(devm.ctx).Warn().Msg("making everything visible!")
|
||||
appctx.Logger().Warn().Msg("making everything visible!")
|
||||
devm.scm.SetScreen(devm.scm.PreviousScreen)
|
||||
break
|
||||
case "p":
|
||||
|
@ -1,7 +1,6 @@
|
||||
package screens
|
||||
|
||||
import (
|
||||
"context"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/ecs"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/ecs/systems"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/fov"
|
||||
@ -15,7 +14,6 @@ import (
|
||||
)
|
||||
|
||||
type GameScreen struct {
|
||||
ctx context.Context
|
||||
mw *mainwindow.MainWindow
|
||||
state *gamestate.GameState
|
||||
vp *mainwindow.ViewPort
|
||||
@ -24,9 +22,8 @@ type GameScreen struct {
|
||||
fov fov.Fov
|
||||
}
|
||||
|
||||
func NewGameScreen(ctx context.Context, mw *mainwindow.MainWindow, state *gamestate.GameState, viewPort *mainwindow.ViewPort, controller *ecs.Controller, scm *types.ScreenManager) *GameScreen {
|
||||
func NewGameScreen(mw *mainwindow.MainWindow, state *gamestate.GameState, viewPort *mainwindow.ViewPort, controller *ecs.Controller, scm *types.ScreenManager) *GameScreen {
|
||||
ts := &GameScreen{
|
||||
ctx: ctx,
|
||||
mw: mw,
|
||||
state: state,
|
||||
vp: viewPort,
|
||||
@ -49,7 +46,7 @@ func (ts *GameScreen) UseEcs() bool { return true }
|
||||
func (ts *GameScreen) Enter() {
|
||||
ts.mw.GetLayer("overlay").ClearArea(0, ts.mw.H-3, 30, 3)
|
||||
ts.mw.GetLayer("overlay").WithColor("#77777777").
|
||||
Print(1, ts.mw.H-2, "Press [color=white]?[/color] for help")
|
||||
Print(ts.mw.W - 17 , 1, "Press [color=white]?[/color] for help")
|
||||
}
|
||||
func (ts *GameScreen) Exit() {
|
||||
//trs := ts.controller.GetSystem(ecs.LevelRenderSystem)
|
||||
@ -58,6 +55,7 @@ func (ts *GameScreen) Exit() {
|
||||
//remove what we dont need
|
||||
}
|
||||
|
||||
//fixme kry names to action constants!
|
||||
func (ts *GameScreen) HandleInput(input string) {
|
||||
//ts.state.Do(func(){
|
||||
switch input {
|
||||
@ -100,13 +98,22 @@ func (ts *GameScreen) HandleInput(input string) {
|
||||
} //do nothing
|
||||
//select if there is more than 1
|
||||
if len(carrieds) > 1 {
|
||||
appctx.Logger(ts.ctx).Warn().Msg("Passing item list to inventory not implemented yet")
|
||||
appctx.Logger().Warn().Msg("Passing item list to inventory not implemented yet")
|
||||
} else {
|
||||
//call pickup in selected
|
||||
cc := items.Controller.GetComponent(carrieds[0], ecs.CarriedComponent).(items.Carried)
|
||||
items.Carried.Pickup(cc, ts.state.Player, carrieds[0])
|
||||
}
|
||||
err := items.Carried.Pickup(cc, ts.state.Player, carrieds[0])
|
||||
if err != nil {
|
||||
// Message with error
|
||||
//gameLog.Log.Error(err)
|
||||
//@fixme!
|
||||
appctx.Logger().Warn().Err(err)
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
//log picked up
|
||||
//gameLog.Log.Message(err)
|
||||
break;
|
||||
|
||||
case "i":
|
||||
|
@ -63,6 +63,8 @@ func (is *InventoryScreen) Enter() {
|
||||
is.prepared.Prepare(is)
|
||||
}
|
||||
|
||||
//fixme key names to action constants!
|
||||
//fixme unify scrolling controls!
|
||||
func (is *InventoryScreen) HandleInput(input string) {
|
||||
if strings.Contains(string(runeIndex), strings.Replace(input, "Shift+", "", -1)) {
|
||||
if strings.Contains("Shift+", input) {
|
||||
@ -76,7 +78,7 @@ func (is *InventoryScreen) HandleInput(input string) {
|
||||
return
|
||||
}
|
||||
switch input {
|
||||
case "Up":
|
||||
case "Up", "k":
|
||||
is.cursor = is.cursor - 1
|
||||
if is.cursor < 0 {
|
||||
is.cursor = 0
|
||||
@ -88,7 +90,7 @@ func (is *InventoryScreen) HandleInput(input string) {
|
||||
}
|
||||
}
|
||||
break
|
||||
case "Down":
|
||||
case "Down", "j":
|
||||
is.cursor = is.cursor + 1
|
||||
if is.cursor >= len(is.prepared) {
|
||||
is.cursor = len(is.prepared) - 1
|
||||
@ -119,7 +121,8 @@ func (is *InventoryScreen) HandleInput(input string) {
|
||||
}
|
||||
break
|
||||
case "enter":
|
||||
//select current under cursor
|
||||
//show actions menu for item under cursor
|
||||
//fixme implement
|
||||
break;
|
||||
case "Escape":
|
||||
fallthrough
|
||||
@ -143,7 +146,12 @@ func (is *InventoryScreen) InventoryRender() {
|
||||
footerHeight = footerHeight + 2
|
||||
}
|
||||
_, 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{
|
||||
X: is.X,
|
||||
Y: is.Y + headerHeight + 1,
|
||||
W: is.W,
|
||||
H: is.H - headerHeight - footerHeight,
|
||||
}
|
||||
_ = itemField
|
||||
is.pageSize = itemField.H - 2
|
||||
|
||||
|
@ -1,166 +1,166 @@
|
||||
package screens
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
"lab.zaar.be/thefish/alchemyst-go/ui/mainwindow"
|
||||
blt "lab.zaar.be/thefish/bearlibterminal"
|
||||
"strings"
|
||||
"fmt"
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
"lab.zaar.be/thefish/alchemyst-go/ui/mainwindow"
|
||||
blt "lab.zaar.be/thefish/bearlibterminal"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const runeIndex = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
type MenuScreen struct {
|
||||
types.Rect
|
||||
types.Rect
|
||||
|
||||
mw *mainwindow.MainWindow
|
||||
scm *types.ScreenManager
|
||||
renderParent bool
|
||||
mw *mainwindow.MainWindow
|
||||
scm *types.ScreenManager
|
||||
renderParent bool
|
||||
|
||||
items []interface{}
|
||||
offset int
|
||||
items []interface{}
|
||||
offset int
|
||||
|
||||
drawFunc func()
|
||||
inputFunc func(string)
|
||||
drawFunc func()
|
||||
inputFunc func(string)
|
||||
|
||||
title string
|
||||
header string
|
||||
footer string
|
||||
redraw bool
|
||||
bgColor string
|
||||
fgColor string
|
||||
title string
|
||||
header string
|
||||
footer string
|
||||
redraw bool
|
||||
bgColor string
|
||||
fgColor string
|
||||
}
|
||||
|
||||
func NewMenuScreen(mw *mainwindow.MainWindow, scm *types.ScreenManager, title, header, footer string, rect types.Rect, renderParent bool) *MenuScreen {
|
||||
return &MenuScreen{
|
||||
title: title,
|
||||
header: header,
|
||||
footer: footer,
|
||||
Rect: rect,
|
||||
mw: mw,
|
||||
scm: scm,
|
||||
renderParent: renderParent,
|
||||
}
|
||||
return &MenuScreen{
|
||||
title: title,
|
||||
header: header,
|
||||
footer: footer,
|
||||
Rect: rect,
|
||||
mw: mw,
|
||||
scm: scm,
|
||||
renderParent: renderParent,
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *MenuScreen) MakeList() *MenuScreen {
|
||||
ms.drawFunc = ms.ListRender
|
||||
ms.inputFunc = ms.ListHandleInput
|
||||
return ms
|
||||
ms.drawFunc = ms.ListRender
|
||||
ms.inputFunc = ms.ListHandleInput
|
||||
return ms
|
||||
}
|
||||
|
||||
func (ms *MenuScreen) SetBgColor(color string) *MenuScreen {
|
||||
ms.bgColor = color
|
||||
return ms
|
||||
ms.bgColor = color
|
||||
return ms
|
||||
}
|
||||
|
||||
func (ms *MenuScreen) SetFgColor(color string) *MenuScreen {
|
||||
ms.fgColor = color
|
||||
return ms
|
||||
ms.fgColor = color
|
||||
return ms
|
||||
}
|
||||
|
||||
//fixme!!
|
||||
|
||||
func (ms *MenuScreen) SetItems(items []interface{}) *MenuScreen {
|
||||
ms.items = items
|
||||
return ms
|
||||
ms.items = items
|
||||
return ms
|
||||
}
|
||||
|
||||
func (ms *MenuScreen) UseEcs() bool { return false }
|
||||
|
||||
func (ms *MenuScreen) Enter() {
|
||||
ms.redraw = true
|
||||
ms.offset = 0
|
||||
ms.redraw = true
|
||||
ms.offset = 0
|
||||
}
|
||||
|
||||
func (ms *MenuScreen) HandleInput(input string) {
|
||||
ms.inputFunc(input)
|
||||
ms.inputFunc(input)
|
||||
}
|
||||
|
||||
func (ms *MenuScreen) Exit() {
|
||||
menuLayer := ms.mw.GetLayer("menu")
|
||||
menuLayer.ClearRect(ms.Rect)
|
||||
bgLayer := ms.mw.GetLayer("menubg")
|
||||
bgLayer.ClearRect(ms.Rect)
|
||||
menuLayer := ms.mw.GetLayer("menu")
|
||||
menuLayer.ClearRect(ms.Rect)
|
||||
bgLayer := ms.mw.GetLayer("menubg")
|
||||
bgLayer.ClearRect(ms.Rect)
|
||||
}
|
||||
|
||||
func (ms *MenuScreen) Render() {
|
||||
if ms.renderParent {
|
||||
ms.scm.PreviousScreen.Render()
|
||||
}
|
||||
if (ms.redraw || ms.renderParent) {
|
||||
ms.redraw = false
|
||||
ms.drawFunc()
|
||||
}
|
||||
if ms.renderParent {
|
||||
ms.scm.PreviousScreen.Render()
|
||||
}
|
||||
if ms.redraw || ms.renderParent {
|
||||
ms.redraw = false
|
||||
ms.drawFunc()
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *MenuScreen) ListHandleInput(input string) {
|
||||
switch input {
|
||||
case "Up":
|
||||
ms.offset = ms.offset - 1
|
||||
if ms.offset < 0 {
|
||||
ms.offset = 0
|
||||
}
|
||||
break
|
||||
case "Down":
|
||||
ms.offset = ms.offset + 1
|
||||
if ms.offset > len(ms.items)-1 {
|
||||
ms.offset = len(ms.items) - 1
|
||||
}
|
||||
break
|
||||
case "Escape":
|
||||
fallthrough
|
||||
case "Space":
|
||||
ms.scm.SetScreen(ms.scm.PreviousScreen)
|
||||
break
|
||||
}
|
||||
switch input {
|
||||
case "Up", "k":
|
||||
ms.offset = ms.offset - 1
|
||||
if ms.offset < 0 {
|
||||
ms.offset = 0
|
||||
}
|
||||
break
|
||||
case "Down", "j":
|
||||
ms.offset = ms.offset + 1
|
||||
if ms.offset > len(ms.items)-1 {
|
||||
ms.offset = len(ms.items) - 1
|
||||
}
|
||||
break
|
||||
case "Escape":
|
||||
fallthrough
|
||||
case "Space":
|
||||
ms.scm.SetScreen(ms.scm.PreviousScreen)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *MenuScreen) ListRender() {
|
||||
menuLayer := ms.mw.GetLayer("menu")
|
||||
menuLayer.ClearRect(ms.Rect)
|
||||
bgLayer := ms.mw.GetLayer("menubg")
|
||||
bgLayer.ClearRect(ms.Rect)
|
||||
bgLayer.WithColor(ms.bgColor).NewWindow(ms.Rect).Splash()
|
||||
menuLayer.WithColor(ms.fgColor).NewWindow(ms.Rect).DoubleBordered(ms.title)
|
||||
menuLayer.Print(ms.X+(ms.W/2)-7, ms.Y+ms.H-1, "╡"+"[color=green]Space[/color] to close"+"╞")
|
||||
footerHeight := 0
|
||||
if ms.footer != "" {
|
||||
_, footerHeight = menuLayer.PrintInside(ms.Rect, ms.footer, 9)
|
||||
footerHeight = footerHeight + 2
|
||||
}
|
||||
_, headerHeight := menuLayer.PrintInside(ms.Rect, ms.header, blt.TK_ALIGN_LEFT)
|
||||
itemField := types.Rect{ms.X, ms.Y + headerHeight + 1, ms.W, ms.H - headerHeight - footerHeight}
|
||||
_ = itemField
|
||||
var ilw, ilh int
|
||||
if (len(ms.items) > 0) {
|
||||
//fixme itemfield object, scroller, inputhandler, current selected item
|
||||
menuItems := make([]string, 0)
|
||||
for i := ms.offset; i < len(ms.items); i++ {
|
||||
if string(ms.items[i].(string)) != "" {
|
||||
menuItems = append(menuItems, ms.items[i].(string))
|
||||
}
|
||||
}
|
||||
ilw, ilh = menuLayer.PrintInside(itemField, strings.Join(menuItems, "\n"), blt.TK_ALIGN_LEFT)
|
||||
}
|
||||
if ilh < len(ms.items) {
|
||||
ms.drawScrollBar(menuLayer, itemField)
|
||||
}
|
||||
if ilw > itemField.W-4 {
|
||||
fmt.Printf("Excess width of item names found! Need h-scroll of certain names")
|
||||
}
|
||||
menuLayer := ms.mw.GetLayer("menu")
|
||||
menuLayer.ClearRect(ms.Rect)
|
||||
bgLayer := ms.mw.GetLayer("menubg")
|
||||
bgLayer.ClearRect(ms.Rect)
|
||||
bgLayer.WithColor(ms.bgColor).NewWindow(ms.Rect).Splash()
|
||||
menuLayer.WithColor(ms.fgColor).NewWindow(ms.Rect).DoubleBordered(ms.title)
|
||||
menuLayer.Print(ms.X+(ms.W/2)-7, ms.Y+ms.H-1, "╡"+"[color=green]Space[/color] to close"+"╞")
|
||||
footerHeight := 0
|
||||
if ms.footer != "" {
|
||||
_, footerHeight = menuLayer.PrintInside(ms.Rect, ms.footer, 9)
|
||||
footerHeight = footerHeight + 2
|
||||
}
|
||||
_, headerHeight := menuLayer.PrintInside(ms.Rect, ms.header, blt.TK_ALIGN_LEFT)
|
||||
itemField := types.Rect{ms.X, ms.Y + headerHeight + 1, ms.W, ms.H - headerHeight - footerHeight}
|
||||
_ = itemField
|
||||
var ilw, ilh int
|
||||
if len(ms.items) > 0 {
|
||||
//fixme itemfield object, scroller, inputhandler, current selected item
|
||||
menuItems := make([]string, 0)
|
||||
for i := ms.offset; i < len(ms.items); i++ {
|
||||
if string(ms.items[i].(string)) != "" {
|
||||
menuItems = append(menuItems, ms.items[i].(string))
|
||||
}
|
||||
}
|
||||
ilw, ilh = menuLayer.PrintInside(itemField, strings.Join(menuItems, "\n"), blt.TK_ALIGN_LEFT)
|
||||
}
|
||||
if ilh < len(ms.items) {
|
||||
ms.drawScrollBar(menuLayer, itemField)
|
||||
}
|
||||
if ilw > itemField.W-4 {
|
||||
fmt.Printf("Excess width of item names found! Need h-scroll of certain names")
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *MenuScreen) drawScrollBar(menuLayer *mainwindow.Layer, itemField types.Rect) {
|
||||
scrollbarBg := types.NewRect(itemField.X+itemField.W-2, itemField.Y + 1, 1, itemField.H - 4)
|
||||
menuLayer.WithColor("#77000000").NewWindow(scrollbarBg).Splash()
|
||||
//tick
|
||||
menuLayer.WithColor(ms.fgColor).Put(
|
||||
scrollbarBg.X,
|
||||
scrollbarBg.Y + int(float64(ms.offset) / float64(len(ms.items)) * float64(scrollbarBg.H)),
|
||||
"⏹",
|
||||
)
|
||||
menuLayer.WithColor(ms.fgColor).Put(itemField.X+itemField.W-2, itemField.Y+scrollbarBg.H + 1, "↓")
|
||||
menuLayer.WithColor(ms.fgColor).Put(itemField.X+itemField.W-2, itemField.Y, "↑")
|
||||
scrollbarBg := types.NewRect(itemField.X+itemField.W-2, itemField.Y+1, 1, itemField.H-4)
|
||||
menuLayer.WithColor("#77000000").NewWindow(scrollbarBg).Splash()
|
||||
//tick
|
||||
menuLayer.WithColor(ms.fgColor).Put(
|
||||
scrollbarBg.X,
|
||||
scrollbarBg.Y+int(float64(ms.offset)/float64(len(ms.items))*float64(scrollbarBg.H)),
|
||||
"⏹",
|
||||
)
|
||||
menuLayer.WithColor(ms.fgColor).Put(itemField.X+itemField.W-2, itemField.Y+scrollbarBg.H+1, "↓")
|
||||
menuLayer.WithColor(ms.fgColor).Put(itemField.X+itemField.W-2, itemField.Y, "↑")
|
||||
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ func (ts *TitleScreen) UseEcs() bool { return false }
|
||||
func (ts *TitleScreen) Enter() {
|
||||
blt.Clear()
|
||||
}
|
||||
|
||||
//fixme key names to action constants!
|
||||
func (ts *TitleScreen) HandleInput(input string) {
|
||||
switch input {
|
||||
case "n":
|
||||
@ -55,5 +57,5 @@ Roguebasin Libtcod Tutorial (c) 2010-2011, Jotaf Henriques
|
||||
Brogue 1.3 (c) 2010 Brian Walker
|
||||
Madness (c) 2010 hmp <humpolec@gmail.com>
|
||||
BearLibTerminal (c) Cfyz 2009-2019 <http://foo.wyrd.name/en:bearlibterminal>
|
||||
Gogue (c) jcerise
|
||||
Gogue (c) 2019 jcerise
|
||||
`
|
||||
|
@ -1,7 +1,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"lab.zaar.be/thefish/alchemyst-go/util/appctx"
|
||||
)
|
||||
|
||||
@ -14,16 +13,14 @@ type Screen interface {
|
||||
}
|
||||
|
||||
type ScreenManager struct {
|
||||
ctx context.Context
|
||||
Screens map[string]Screen
|
||||
CurrentScreen Screen
|
||||
PreviousScreen Screen
|
||||
}
|
||||
|
||||
// NewScreenManager is a convenience/constructor method to properly initialize a new ScreenManager
|
||||
func NewScreenManager(ctx context.Context) *ScreenManager {
|
||||
func NewScreenManager() *ScreenManager {
|
||||
manager := ScreenManager{
|
||||
ctx:ctx,
|
||||
Screens: make(map[string]Screen),
|
||||
CurrentScreen: nil,
|
||||
}
|
||||
@ -36,7 +33,7 @@ func (sm *ScreenManager) AddScreen(screenName string, screen Screen) {
|
||||
// A screen with the given name does not yet exist on the ScreenManager, go ahead and add it
|
||||
sm.Screens[screenName] = screen
|
||||
} else {
|
||||
appctx.Logger(sm.ctx).Warn().Msgf("A screen with name %v was already added to the ScreenManager %v!", screenName, sm)
|
||||
appctx.Logger().Warn().Msgf("A screen with name %v was already added to the ScreenManager %v!", screenName, sm)
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +46,7 @@ func (sm *ScreenManager) RemoveScreen(screenName string, screen Screen) {
|
||||
delete(sm.Screens, screenName)
|
||||
} else {
|
||||
// A screen with the given name does not exist
|
||||
appctx.Logger(sm.ctx).Warn().Msgf("A screen with name %v was not found on ScreenManager %v!", screenName, sm)
|
||||
appctx.Logger().Warn().Msgf("A screen with name %v was not found on ScreenManager %v!", screenName, sm)
|
||||
}
|
||||
}
|
||||
|
||||
@ -84,6 +81,6 @@ func (sm *ScreenManager) SetScreenByName(screenName string) {
|
||||
sm.CurrentScreen.Enter()
|
||||
} else {
|
||||
// A screen with the given name does not exist
|
||||
appctx.Logger(sm.ctx).Warn().Msgf("A screen with name %v was not found on ScreenManager %v!", screenName, sm)
|
||||
appctx.Logger().Warn().Msgf("A screen with name %v was not found on ScreenManager %v!", screenName, sm)
|
||||
}
|
||||
}
|
3
go.mod
3
go.mod
@ -6,5 +6,6 @@ require (
|
||||
github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622
|
||||
github.com/rs/zerolog v1.15.0
|
||||
github.com/shopspring/decimal v1.3.1
|
||||
lab.zaar.be/thefish/bearlibterminal v0.0.0-20191018101635-dd37bbc90d77
|
||||
github.com/stretchr/testify v1.8.0
|
||||
lab.zaar.be/thefish/bearlibterminal.git v0.0.0-20191018101635-dd37bbc90d77
|
||||
)
|
||||
|
19
go.sum
19
go.sum
@ -1,19 +0,0 @@
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622 h1:lxbhOGZ9pU3Kf8P6lFluUcE82yVZn2EqEf4+mWRNPV0=
|
||||
github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622/go.mod h1:D90+MBHVc9Sk1lJAbEVgws0eYEurY4mv2TDso3Nxh3w=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc h1:N3zlSgxkefUH/ecsl37RWTkESTB026kmXzNly8TuZCI=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
lab.zaar.be/thefish/bearlibterminal v0.0.0-20191018101635-dd37bbc90d77 h1:ElfFSOSxp1PViWH7+iKZ8sZvEhaKN9o3vt13+hX2yaE=
|
||||
lab.zaar.be/thefish/bearlibterminal v0.0.0-20191018101635-dd37bbc90d77/go.mod h1:tV7Vxx6vf9dPgj9B+RPeSrmtRl8nTSH07HIyBSSnEc4=
|
87
quest-gen.txt
Normal file
87
quest-gen.txt
Normal file
@ -0,0 +1,87 @@
|
||||
Генератор заданий`
|
||||
|
||||
Я на днях понял, что мне не помешал бы генератор миссий от всяческих Гильдий воров и прочих криминальных организаций. Иначе у меня все будет однотипным, а мне, подозреваю, скоро понадобится их много.
|
||||
|
||||
За основу взял генератор случайных ранов из Shadowrun, перелицевал его на чисто-фэнтезийный лад и добавил немного деталей, которые могут мне понадобиться. Собственно, результат:
|
||||
|
||||
(Имка как-то безобразно обрабатывает и без того не очень симпатичные табличные тэги, так что таблицы переписаны в лоб)
|
||||
|
||||
Место встречи
|
||||
|
||||
1 Таверна
|
||||
2 Склад, доки
|
||||
3 Пустырь, трущобы
|
||||
4 Движущийся транспорт
|
||||
5 Связь через артефакт
|
||||
6 Сновидения
|
||||
|
||||
Наниматель
|
||||
|
||||
2 Тайное общество
|
||||
3 Престол
|
||||
4 Купец
|
||||
5-6 Человек у власти
|
||||
7-8 Криминальный синдикат
|
||||
9 Гильдия
|
||||
10 Частное лицо
|
||||
11 Магическое общество
|
||||
12 Необычное существо
|
||||
|
||||
Тип задания
|
||||
|
||||
1 Кража
|
||||
2 Убийство или разрушение
|
||||
3 Извлечение или внедрение агента
|
||||
4 Наведение на ложный след
|
||||
5 Защита
|
||||
6 Доставка
|
||||
|
||||
Цель
|
||||
|
||||
1 Ценный человек
|
||||
2 Редкое заклинание
|
||||
3 Важный ритуал
|
||||
4 Магическое существо
|
||||
5 Волшебный предмет
|
||||
6 Здание или сооружение
|
||||
|
||||
Осложнение
|
||||
|
||||
1 Охрана лучше, чем ожидалось
|
||||
2 Вовлечена третья сторона
|
||||
3 Цель — не то, чем казалась
|
||||
4 Требуется редкое снаряжение
|
||||
5 Цель переместили или перемещают
|
||||
6 Наниматель подставил команду
|
||||
|
||||
Место задания
|
||||
|
||||
1 Поместье
|
||||
2 Гробница
|
||||
3 Храм
|
||||
4 Дворец
|
||||
5 Фабрика
|
||||
6 Склад
|
||||
|
||||
Оплата
|
||||
|
||||
2 Обучение
|
||||
3 Влияние на кого-то
|
||||
4 Доступ к чему-то
|
||||
5 Снаряжение
|
||||
6-8 Деньги
|
||||
9 Товары, сокровища
|
||||
10 Услуги
|
||||
11 Информация
|
||||
12 Магический артефакт
|
||||
|
||||
Охрана
|
||||
|
||||
Количество бросков пропорционально защищенности цели и места
|
||||
|
||||
1 Вооруженный патруль
|
||||
2 Ловушки
|
||||
3 Магические существа
|
||||
4 Головоломки
|
||||
5 Заклинания
|
||||
6 Пассивные преграды
|
@ -0,0 +1,14 @@
|
||||
1 Почему Go?
|
||||
---
|
||||
|
||||
потому что круто. (тут бла-бла про управление памятью, многопоточность, сборку и либы)
|
||||
|
||||
2 Почему Libbearterminal?
|
||||
---
|
||||
|
||||
Дьявол предложил расчесать манту.
|
||||
|
||||
3 Почему нет звука?
|
||||
---
|
||||
|
||||
Бля, да лень возиться. И ни к чему это тут.
|
@ -155,6 +155,9 @@ func (tr *TerrainRender) Render() {
|
||||
...
|
||||
}
|
||||
```
|
||||
TODO: троттлинг
|
||||
|
||||
|
||||
|
||||
#### Каналы состояний и их Listеner-ы
|
||||
|
||||
@ -167,7 +170,7 @@ func (tr *TerrainRender) Render() {
|
||||
|
||||
- reflect в main loop. Лишь **только** выкинув рефлкесию и больше ничего не делая - я снизил потребление CPU приложением
|
||||
**вдвое**. Это удобная штука, не спорю, но пользоваться ей надо при загрузке ресурсов, при сохранении/загрузке состояния
|
||||
приложения - т.е. при разовых операциях. Как оказалось, она _очень_ дорогая по CPU. Кто пользоуется ей в main loop, ORM
|
||||
приложения - т.е. при разовых операциях. Как оказалось, она _очень_ дорогая по CPU. Кто пользуется ей в main loop, ORM
|
||||
и прочих нагруженных местах - да будет предан анафеме.
|
||||
|
||||
|
||||
|
@ -4,6 +4,8 @@ RLG и Golang - некоторые полезные советы
|
||||
1. [Установка и некоторые особенности работы](linux_go_blt_install_quickstart.md) связки BLT + Go на Linux
|
||||
2. Что [стоит и НЕ стоит](go_game_dos_and_donts.md) делать с возможностями Go - +chans, +tickers, +throttling, -closures
|
||||
3. [Система типов](./static_types_vs_ecs.md) - нативная или ECS? На самом деле и то, и то
|
||||
4. Немножко конкретики: [предметы и обращение с ними](./item_objecttypes_and_blueprints.md). Как правильно готовить
|
||||
предметы - чтобы потом не было мучительно больно.
|
||||
|
||||
Дополнения
|
||||
---
|
||||
|
16
story/item_objecttypes_and_blueprints.md
Normal file
16
story/item_objecttypes_and_blueprints.md
Normal file
@ -0,0 +1,16 @@
|
||||
Blueprints, паттерн Object Type и сериализация
|
||||
==
|
||||
|
||||
Посмотрите внимательно вот это видео, [чувак дело говорит](https://www.youtube.com/watch?v=JxI3Eu5DPwE).
|
||||
|
||||
|
||||
Итого:
|
||||
|
||||
- Всё что может делать предмет - в типы - или компоненты, если решились на ECS.
|
||||
- Суперкласс/архетип для предмета
|
||||
- Всё данные предмета - в человекочитаемый формат, json например
|
||||
- Код для сериализации данных в экземпляр а памяти и обратно - **тщательно тестируем**! (TODO: примеры смешных багов)
|
||||
- Названия типов - тоже в json, ни грамма данных врагу (т.е. коду). Позволит быстро менять/модифицировать игру чуть ли
|
||||
не текстовым редактором.
|
||||
|
||||
И кстати. Чертежи не только на предметы работают, но об этом с следующей главе.
|
@ -150,4 +150,4 @@ func renderSuperEffect() {
|
||||
выполняется в main loop. В целом картина именно такая, но больше подробностей можно
|
||||
найти по ссылкам в комментариях.
|
||||
|
||||
[1]: Если такой контейнер аккуратно сериализовать (рекурсивно вместе со всем содержимым) и записать на диск... То потом можно его прочитать и десериализовать. Получив тем самым почти бесплатно Save / Load.
|
||||
[1]: Если такой контейнер аккуратно сериализовать (рекурсивно вместе со всем содержимым) и записать на диск... То потом можно его прочитать и десериализовать. Получив тем самым почти бесплатно Save / Load.
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
Почему это важно
|
||||
---
|
||||
Про сборку под разные ОС я даже уюеждать не буду - аудитория рогаликов мало того что крохотная, так еще и сильно
|
||||
Про сборку под разные ОС я даже убеждать не буду - аудитория рогаликов мало того что крохотная, так еще и сильно
|
||||
сегментирована по осям. Go почти бесплатно дает вам возможность сборки под все мажорные оси, пользуйтесь этим - и
|
||||
потенциально в разы больше народа ознакомится с вашим творением.
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
Система типов в Go
|
||||
Система типов и Go
|
||||
---
|
||||
|
||||
Плюсы использования нативной системы типов
|
||||
|
||||
|
@ -15,7 +15,7 @@ type MainWindow struct {
|
||||
}
|
||||
|
||||
func Init(ctx context.Context) *MainWindow {
|
||||
appctx.Logger(ctx).Info().Msgf("Opening main window...")
|
||||
appctx.Logger().Info().Msgf("Opening main window...")
|
||||
mw := MainWindow{ctx: ctx, layers: make(map[string]types.Renderable, 0)}
|
||||
mw.Open()
|
||||
return &mw
|
||||
@ -33,7 +33,7 @@ func (mw *MainWindow) GetLayer(name string) *Layer {
|
||||
if layer, ok := mw.layers[name]; ok {
|
||||
return layer.(*Layer)
|
||||
}
|
||||
appctx.Logger(mw.ctx).Fatal().Msgf("No layer with such name %s", name)
|
||||
appctx.Logger().Fatal().Msgf("No layer with such name %s", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ func (mw *MainWindow) Open() {
|
||||
}
|
||||
|
||||
func (mw *MainWindow) Close() {
|
||||
appctx.Logger(mw.ctx).Info().Msg("Closing main window...")
|
||||
appctx.Logger().Info().Msg("Closing main window...")
|
||||
blt.Close()
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ type ViewPort struct {
|
||||
|
||||
func NewViewPort(x, y, w, h int) *ViewPort {
|
||||
vp := ViewPort{
|
||||
Rect: types.Rect{x, y, w, h},
|
||||
Rect: types.Rect{X: x, Y: y, W: w, H: h},
|
||||
}
|
||||
return &vp
|
||||
}
|
||||
@ -47,8 +47,8 @@ func (vp *ViewPort) ToVPCoords(c types.Coords) (newCoords types.Coords, err erro
|
||||
//coords on map to coords on vp
|
||||
x, y := c.X-vp.CameraCoords.X, c.Y-vp.CameraCoords.Y
|
||||
if x < 0 || y < 0 || x > vp.W || y > vp.H {
|
||||
return types.Coords{-1, -1}, fmt.Errorf("Not in viewport: {%d, %d}", x, y)
|
||||
return types.Coords{X: -1, Y: -1}, fmt.Errorf("Not in viewport: {%d, %d}", x, y)
|
||||
}
|
||||
return types.Coords{x, y}, nil
|
||||
return types.Coords{X: x, Y: y}, nil
|
||||
}
|
||||
|
||||
|
@ -12,15 +12,17 @@ const (
|
||||
loggerKey = "logger"
|
||||
)
|
||||
|
||||
type ClientCtx struct {
|
||||
type clientCtx struct {
|
||||
context.Context
|
||||
}
|
||||
|
||||
func NewClientContext(config *util.Config, logger *zerolog.Logger) ClientCtx {
|
||||
var ClientState clientCtx
|
||||
|
||||
func NewClientContext(config *util.Config, logger *zerolog.Logger) {
|
||||
ctx := context.Context(context.TODO())
|
||||
ctx = context.WithValue(ctx, configKey, config)
|
||||
ctx = context.WithValue(ctx, loggerKey, logger)
|
||||
return ClientCtx{ ctx}
|
||||
ClientState = clientCtx{ctx}
|
||||
}
|
||||
|
||||
func Config(c context.Context) *util.Config {
|
||||
@ -31,7 +33,11 @@ func Config(c context.Context) *util.Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func Logger(c context.Context) *zerolog.Logger {
|
||||
func Logger() *zerolog.Logger {
|
||||
return getLogger(ClientState.Context)
|
||||
}
|
||||
|
||||
func getLogger(c context.Context) *zerolog.Logger {
|
||||
logger, ok := c.Value(loggerKey).(*zerolog.Logger)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("no access to logger from context"))
|
||||
|
@ -6,6 +6,7 @@ import "lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
|
||||
var nodeId = 0
|
||||
var nodeList = make(map[types.Coords]Node, 0)
|
||||
|
||||
// Node defines a struct having as components the node X and Y coordinate position.
|
||||
type Node struct {
|
||||
Id int
|
||||
@ -129,17 +130,27 @@ func (d *Delaunay) Init(width, height int) *Delaunay {
|
||||
}
|
||||
|
||||
var supertriangle1, supertriangle2 Triangle
|
||||
|
||||
// clear method clears the delaunay triangles slice.
|
||||
func (d *Delaunay) clear() {
|
||||
p0 := newNode(types.Coords{0,0})
|
||||
p1 := newNode(types.Coords{d.width, 0})
|
||||
p2 := newNode(types.Coords{d.width, d.height})
|
||||
p3 := newNode(types.Coords{0, d.height})
|
||||
p0 := newNode(types.Coords{X: 0, Y: 0})
|
||||
p1 := newNode(types.Coords{X: d.width, Y: 0})
|
||||
p2 := func() Node {
|
||||
var coords types.Coords = types.Coords{X: d.width, Y: d.height}
|
||||
if n, ok := nodeList[coords]; ok {
|
||||
return n
|
||||
}
|
||||
neue := Node{Id: nodeId, Coords: coords}
|
||||
nodeList[coords] = neue
|
||||
nodeId++
|
||||
return neue
|
||||
}()
|
||||
p3 := newNode(types.Coords{X: 0, Y: d.height})
|
||||
|
||||
// Create the supertriangle, an artificial triangle which encompasses all the points.
|
||||
// At the end of the triangulation process any triangles which share Edges with the supertriangle are deleted from the triangle list.
|
||||
supertriangle1 = t.newTriangle(p0,p1,p2)
|
||||
supertriangle2 = t.newTriangle(p0,p2,p3)
|
||||
supertriangle1 = t.newTriangle(p0, p1, p2)
|
||||
supertriangle2 = t.newTriangle(p0, p2, p3)
|
||||
d.triangles = []Triangle{supertriangle1, supertriangle2}
|
||||
}
|
||||
|
||||
@ -199,7 +210,7 @@ func (d *Delaunay) Insert(points []types.Coords) *Delaunay {
|
||||
}
|
||||
for i = 0; i < len(polygon); i++ {
|
||||
edge := polygon[i]
|
||||
temps = append(temps, t.newTriangle(edge.Nodes[0], edge.Nodes[1], newNode(types.Coords{x, y})))
|
||||
temps = append(temps, t.newTriangle(edge.Nodes[0], edge.Nodes[1], newNode(types.Coords{X: x, Y: y})))
|
||||
}
|
||||
d.triangles = temps
|
||||
}
|
||||
@ -245,12 +256,9 @@ func (d *Delaunay) GetTriangles() []Triangle {
|
||||
}
|
||||
|
||||
func (d *Delaunay) GetEdges() []Edge {
|
||||
edges := make([]Edge, 0)
|
||||
for _, trs := range d.triangles {
|
||||
for _, e := range trs.Edges {
|
||||
edges = append(edges, e)
|
||||
}
|
||||
|
||||
}
|
||||
return edges
|
||||
edges := make([]Edge, 0)
|
||||
for _, trs := range d.triangles {
|
||||
edges = append(edges, trs.Edges...)
|
||||
}
|
||||
return edges
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ func GetTriangles(coords []types.Coords, w, h int) []types.Edge {
|
||||
edges := d.Init(100, 100).Insert(coords).GetEdges()
|
||||
output := make([]types.Edge, 0)
|
||||
for _, e := range edges{
|
||||
output = append(output, types.Edge{e.Nodes[0].Coords, e.Nodes[1].Coords})
|
||||
output = append(output, types.Edge{From: e.Nodes[0].Coords, To: e.Nodes[1].Coords})
|
||||
}
|
||||
return output
|
||||
}
|
||||
@ -46,16 +46,16 @@ func GetMst(coords []types.Coords, w, h, negativeWeight int) []types.Edge {
|
||||
graph = append(
|
||||
graph,
|
||||
kruskals.SimpleWeightedEdge{
|
||||
nodeMap[e.Nodes[0].Id],
|
||||
nodeMap[e.Nodes[1].Id],
|
||||
negativeWeight - int(e.Nodes[0].Coords.DistanceTo(e.Nodes[1].Coords))},
|
||||
F: nodeMap[e.Nodes[0].Id],
|
||||
T: nodeMap[e.Nodes[1].Id],
|
||||
W: negativeWeight - int(e.Nodes[0].Coords.DistanceTo(e.Nodes[1].Coords))},
|
||||
)
|
||||
}
|
||||
|
||||
result := kruskals.MinimumSpanningTree(graph)
|
||||
output := make([]types.Edge, 0)
|
||||
for _, we := range result{
|
||||
output = append(output, types.Edge{nodeList[we.From()].Coords, nodeList[we.To()].Coords})
|
||||
output = append(output, types.Edge{From: nodeList[we.From()].Coords, To: nodeList[we.To()].Coords})
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
78
util/wu/line.go
Normal file
78
util/wu/line.go
Normal file
@ -0,0 +1,78 @@
|
||||
package wu
|
||||
|
||||
import (
|
||||
"lab.zaar.be/thefish/alchemyst-go/engine/types"
|
||||
"math"
|
||||
)
|
||||
|
||||
func ipart(x float64) float64 {
|
||||
return math.Floor(x)
|
||||
}
|
||||
|
||||
func round(x float64) float64 {
|
||||
return ipart(x + .5)
|
||||
}
|
||||
|
||||
func fpart(x float64) float64 {
|
||||
return x - ipart(x)
|
||||
}
|
||||
|
||||
func rfpart(x float64) float64 {
|
||||
return 1 - fpart(x)
|
||||
}
|
||||
|
||||
func (Layer types.Putable) WuLine(x1, y1, x2, y2 float64, w int) {
|
||||
dx := x2 - x1
|
||||
dy := y2 - y1
|
||||
ax := dx
|
||||
if ax < 0 {
|
||||
ax = -ax
|
||||
}
|
||||
ay := dy
|
||||
if ay < 0 {
|
||||
ay = -ay
|
||||
}
|
||||
|
||||
var plot func(int, int, float64)
|
||||
|
||||
if ax < ay {
|
||||
x1, y1 = y1, x1
|
||||
x2, y2 = y2, x2
|
||||
dx, dy = dy, dx
|
||||
plot = func(x, y int, c float64) {
|
||||
Layer.Put(y, x, uint8(255 * c))
|
||||
}
|
||||
} else {
|
||||
plot = func(x, y int, c float64) {
|
||||
Layer.Put(x, y, uint8(255 * c))
|
||||
}
|
||||
}
|
||||
if x2 < x1 {
|
||||
x1, x2 = x2, x1
|
||||
y1, y2 = y2, y1
|
||||
}
|
||||
gradient := dy / dx
|
||||
|
||||
xend := round(x1)
|
||||
yend := y1 + gradient*(xend-x1)
|
||||
xgap := rfpart(x1 + .5)
|
||||
xpxl1 := int(xend)
|
||||
ypxl1 := int(ipart(yend))
|
||||
plot(xpxl1, ypxl1, rfpart(yend)*xgap)
|
||||
plot(xpxl1, ypxl1+1, fpart(yend)*xgap)
|
||||
intery := yend + gradient
|
||||
|
||||
xend = round(x2)
|
||||
yend = y2 + gradient*(xend-x2)
|
||||
xgap = fpart(x2 + 0.5)
|
||||
xpxl2 := int(xend)
|
||||
ypxl2 := int(ipart(yend))
|
||||
plot(xpxl2, ypxl2, rfpart(yend)*xgap)
|
||||
plot(xpxl2, ypxl2+1, fpart(yend)*xgap)
|
||||
|
||||
for x := xpxl1 + 1; x <= xpxl2-1; x++ {
|
||||
plot(x, int(ipart(intery)), rfpart(intery))
|
||||
plot(x, int(ipart(intery))+1, fpart(intery))
|
||||
intery = intery + gradient
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user