Skip to content

Commit

Permalink
feat: new learning
Browse files Browse the repository at this point in the history
  • Loading branch information
leaftail1880 committed Aug 27, 2024
1 parent 9c8825e commit 45d3259
Show file tree
Hide file tree
Showing 15 changed files with 193 additions and 107 deletions.
8 changes: 7 additions & 1 deletion src/lib/cutscene/cutscene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ export class Cutscene {
}
}

get start() {
return this.sections[0]?.points[0]
}

/**
* Plays the Cutscene for the provided player
*
Expand All @@ -95,7 +99,7 @@ export class Cutscene {
Sidebar.forceHide.add(player)

const controller = { cancel: false }
this.forEachPoint(
const promise = this.forEachPoint(
async (point, pointNum, section, sectionNum) => {
if (!player.isValid()) {
controller.cancel = true
Expand Down Expand Up @@ -137,6 +141,8 @@ export class Cutscene {
player,
controller,
})

return promise
}

/**
Expand Down
3 changes: 3 additions & 0 deletions src/lib/quest/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Quest } from './quest'
import { QS, QSBuilder } from './step'

import { QSCounter, QSCounterBuilder } from './steps/counter'
import { QSDialogue } from './steps/dialogue'
import { QSDynamic, QSDynamicBuilder } from './steps/dynamic'
import { QSItem, QSItemBuilder } from './steps/item'
import { QSPlace } from './steps/place'
Expand All @@ -30,6 +31,8 @@ export class PlayerQuest {

place = QSPlace.bind(this)

dialogue = QSDialogue.bind(this)

end = (action: (ctx: PlayerQuest) => void) => {
this.onEnd = action.bind(this, this) as VoidFunction
}
Expand Down
23 changes: 19 additions & 4 deletions src/lib/quest/quest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class Quest {

static playerSettings = Settings.player('Задания\n§7Настройки игровых заданий', 'quest', {
messageForEachStep: {
value: true,
value: false,
name: 'Сообщение в чат при каждом шаге',
description: 'Отправлять ли сообщение в чат при каждом новом разделе задания',
},
Expand All @@ -30,13 +30,28 @@ export class Quest {
static sidebar: import('lib/sidebar').SidebarLineCreate<unknown> = {
create(sidebar) {
const showSidebar = sidebar.show.bind(sidebar)
const textCache = new WeakPlayerMap<{ step: QS; time: number }>({ removeOnLeave: true })

return function (player: Player) {
const current = Quest.getCurrent(player)
if (!current) return ''

current.playerQuest.updateListeners.add(showSidebar)
return `§f§l${current.quest.name}:§r§6 ${current.text()}`

const text = `§l${current.quest.name}:§r§6 ${current.text()}`
const cached = textCache.get(player)

if (cached?.step !== current) {
textCache.set(player, { step: current, time: 6 })
return text
}

if (cached.time <= 0) return text

// Animate
cached.time--
textCache.set(player, cached)
return `${cached.time % 2 !== 0 ? '§c°' : ''}${text}`
}
},
}
Expand Down Expand Up @@ -150,8 +165,8 @@ export class Quest {
/** Sends message and displays title on quest step move */
private stepSwitchVisual(player: Player, step: QS, i: number, restore: boolean) {
if (Quest.playerSettings(player).messageForEachStep) {
const text = step.text()
if (text) player.success(`§f§l${this.name}: §r§6${step.description ? step.description() : step.text()}`)
const text = step.description?.()
if (text) player.success(`§f§l${this.name}: §r§6${text}`)
}

if (i === 0 && !restore) {
Expand Down
40 changes: 40 additions & 0 deletions src/lib/quest/steps/dialogue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ActionForm } from 'lib'
import { Npc } from 'lib/rpg/npc'
import { PlayerQuest } from '../player'
import { QSDynamic } from './dynamic'

export function QSDialogue(this: PlayerQuest, npc: Npc, text = `Поговорите с ${npc.name}`) {
return {
body: (body: string) => ({
buttons: (...buttons: [string, (ctx: QSDynamic, back: VoidFunction) => void][]) => {
const step = this.dynamic(text)
if (npc.location.valid) step.place(npc.location)

step.activate(ctx => {
const show = () => {
const form = new ActionForm(npc.name, body)

for (const [text, callback] of buttons) {
form.addButton(text, callback.bind(null, ctx, show))
}

form.show(this.player)
}

const inter: Npc.OnInteract = event => {
if (event.player.id !== this.player.id) return false
show()
return true
}

npc.questInteractions.add(inter)
return {
cleanup() {
npc.questInteractions.delete(inter)
},
}
})
},
}),
}
}
31 changes: 18 additions & 13 deletions src/lib/rpg/npc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { location } from 'lib/location'
import { t } from 'lib/text'
import { Place } from './place'

declare namespace Npc {
type OnInteract = (event: Omit<PlayerInteractWithEntityBeforeEvent, 'cancel'>) => void | false
export declare namespace Npc {
type OnInteract = (event: Omit<PlayerInteractWithEntityBeforeEvent, 'cancel'>) => boolean
}

export class Npc {
Expand All @@ -29,7 +29,7 @@ export class Npc {

/** Creates new dynamically loadable npc */
constructor(
private point: Place,
readonly point: Place,
private onInteract: Npc.OnInteract,
) {
this.id = point.fullId
Expand All @@ -43,17 +43,17 @@ export class Npc {
Npc.npcs.push(this)
}

addQuestInteraction(interaction: Npc.OnInteract) {
this.questInteractions.add(interaction)
return interaction
}

private questInteractions = new Set<Npc.OnInteract>()
questInteractions = new Set<Npc.OnInteract>()

private onQuestInteraction: Npc.OnInteract = event => {
for (const interaction of this.questInteractions) {
if (interaction(event) !== false) return // Return on first successfull interaction
if (interaction(event)) return true // Return on first successfull interaction
}
return false
}

get name() {
return this.point.name
}

private spawn() {
Expand Down Expand Up @@ -95,7 +95,7 @@ export class Npc {
const component = event.target.getComponent('npc')
const npcName = component ? component.name : event.target.nameTag

if (!npc || npc.onQuestInteraction(event) === false || npc.onInteract(event) === false) {
if (!npc || !(npc.onQuestInteraction(event) || npc.onInteract(event))) {
return event.player.fail(`§f${npcName}: §cЯ не могу с вами говорить. Приходите позже.`)
}
} catch (e) {
Expand Down Expand Up @@ -124,8 +124,13 @@ export class Npc {

if (filteredNpcs.length > 1) {
// More then one? Save only first one, kill others
npc.entity = filteredNpcs.find(e => e.entity.isValid() && e.entity.lifetimeState === EntityLifetimeState.Loaded && e.entity.getComponent('npc')?.skinIndex !== 0)?.entity

npc.entity = filteredNpcs.find(
e =>
e.entity.isValid() &&
e.entity.lifetimeState === EntityLifetimeState.Loaded &&
e.entity.getComponent('npc')?.skinIndex !== 0,
)?.entity

if (npc.entity) filteredNpcs.forEach(e => e.entity.id !== npc.entity?.id && e.entity.remove())
} else {
npc.entity = filteredNpcs[0]?.entity
Expand Down
1 change: 1 addition & 0 deletions src/lib/shop/npc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class ShopNpc {
this.shop = new Shop(place.name, place.fullId)
this.npc = new Npc(place, event => {
this.shop.open(event.player)
return true
})
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/lib/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,15 @@ export class Sidebar<E = any> {
if (Array.isArray(content)) {
this.clearSidebar(player)
for (const [i, tip] of content.entries()) {
player.onScreenDisplay.setTip((i + 1) as 1 | 2 | 3 | 4 | 5, this.wrap(tip ?? '', options.maxWordCount))
player.onScreenDisplay.setTip((i + 1) as 1 | 2 | 3 | 4 | 5, Sidebar.wrap(tip ?? '', options.maxWordCount))
}
} else {
this.clearTips(player)
player.onScreenDisplay.setSidebar(this.wrap(content, options.maxWordCount))
player.onScreenDisplay.setSidebar(Sidebar.wrap(content, options.maxWordCount))
}
}

private wrap(line: string, maxWordCount: number) {
static wrap(line: string, maxWordCount: number) {
return line
.split('\n')
.map(e => util.wrap(e, maxWordCount))
Expand Down
5 changes: 4 additions & 1 deletion src/modules/places/dungeons/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ function getDungeon(player: Player) {
{ structureId: storage.type },
'',
)
region.configureSize()
if (!region.configureSize()) {
player.onScreenDisplay.setActionBar(t.error`Неизвестный данж: ${storage.type}`)
return
}
return region
}

Expand Down
5 changes: 4 additions & 1 deletion src/modules/places/dungeons/dungeon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,14 @@ export class DungeonRegion extends Region {
this.structureFile = structureFiles[this.structureId]
this.structureSize = this.structureFile.size
// console.log('Created dungeon with size', Vector.string(this.structureSize))
}
} else return false

if (this.area instanceof SphereArea) {
this.area.radius = Vector.distance(this.structurePosition, this.area.center)
// console.log('Changed radius of dungeon to', this.area.radius)
}

return true
}

protected defaultPermissions: RegionPermissions = {
Expand Down
4 changes: 2 additions & 2 deletions src/modules/places/lib/npc/jeweler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { Group } from 'lib/rpg/place'
import { ShopNpc } from 'lib/shop/npc'

export class Jeweler extends ShopNpc {
constructor(group: Group) {
super(group.point('jeweler').name('Ювелир'))
constructor(group: Group, point = group.point('jeweler').name('Ювелир')) {
super(point)
this.shop.body(() => 'Украшения я делать пока не умею.\n\n')

this.shop.menu(form => {
Expand Down
49 changes: 24 additions & 25 deletions src/modules/places/mineshaft/algo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,43 +56,42 @@ export function placeOre(brokenLocation: Block, brokenTypeId: string, dimension:
const isDeepslate =
brokenOre?.isDeepslate ?? (brokenTypeId === MinecraftBlockTypes.Deepslate || brokenLocation.y < -3)

const overrideTypeId = getOreTypeId({ player, isDeepslate, ore: brokenOre?.ore })
// console.debug('Overriding ore', overrideTypeId, brokenOre)
const oreTypeId =
(overrideTypeId ?? brokenOre)
? isDeepslate
? b.Deepslate
: b.Stone
: ores.selectOreByChance(isDeepslate, brokenLocation.y)

const place = (block: Block) => {
if (block.isValid() && !block.isAir) {
if (oreTypeId) block.setType(oreTypeId)
}
const place = (block: Block, oreTypeId: string) => {
if (block.isValid() && !block.isAir && oreTypeId) block.setType(oreTypeId)
}

const first = possibleBlocks.randomElement()
place(first)

for (const block of possibleBlocks.filter(e => e !== first)) {
if (Math.randomInt(0, 100) > (brokenOre?.ore.item.groupChance ?? 50)) place(block)
for (const [action] of EventSignal.sortSubscribers(OrePlace)) {
const placed = action({ player, isDeepslate, brokenOre: brokenOre?.ore, possibleBlocks, place, brokenLocation })
if (placed) return
}
}

interface OrePlaceEvent {
player: Player
isDeepslate: boolean
ore: undefined | OreEntry
brokenOre: undefined | OreEntry
brokenLocation: Block
possibleBlocks: Block[]
place: (block: Block, oreTypeId: string) => void
}

export const OrePlace = new EventSignal<OrePlaceEvent, void | string>()
export const OrePlace = new EventSignal<OrePlaceEvent, boolean>()

function getOreTypeId(event: OrePlaceEvent) {
for (const [action] of EventSignal.sortSubscribers(OrePlace)) {
const typeId = action(event)
if (typeId) return typeId
OrePlace.subscribe(({ brokenOre, isDeepslate, brokenLocation, possibleBlocks, place }) => {
const oreTypeId = brokenOre
? isDeepslate
? b.Deepslate
: b.Stone
: ores.selectOreByChance(isDeepslate, brokenLocation.y)
const first = possibleBlocks.randomElement()
place(first, oreTypeId)

for (const block of possibleBlocks.filter(e => e !== first)) {
if (Math.randomInt(0, 100) > (brokenOre?.item.groupChance ?? 50)) place(block, oreTypeId)
}
}

return true
}, -1)

/**
* @example
Expand Down
27 changes: 18 additions & 9 deletions src/modules/places/stone-quarry/quests/investigating.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import { isNotPlaying, Temporary } from 'lib'
import { isNotPlaying, Vector } from 'lib'
import { Quest } from 'lib/quest/index'
import { RegionEvents } from 'lib/region/events'
import { StoneQuarry } from 'modules/places/stone-quarry/stone-quarry'

class StoneQuarryInvestigating {
place = StoneQuarry
private place = StoneQuarry

quest = new Quest('StoneQuarryInvestigating', StoneQuarry.name, 'Исследуйте новый город!', (q, p) => {
q.dynamic('поговорите с горожанами')
.description('Лучше всего узнавать о городе у местных, поговорите с ними!')
.activate(ctx => {
return new Temporary(() => {
// TODO
})
if (!StoneQuarry.safeArea || !StoneQuarry.cutscene.start) return q.failed('Каменеломня не настроена')

if (!StoneQuarry.safeArea.area.isVectorIn(p.location, p.dimension.type)) {
q.place(...Vector.around(StoneQuarry.cutscene.start, 4), 'Доберитесь до города!')
q.dynamic('Обзор города').activate(ctx => {
this.place.cutscene.play(ctx.player)?.finally(() => ctx.next())
})
}
q.dialogue(this.place.commonOvener.npc)
.body('ПРивет я продаю печки дааааааа')
.buttons([
'Хоршо отлично я у тебя их куплю и переплавлю руду',
ctx => {
ctx.next()
},
])
})

constructor() {
Expand All @@ -28,4 +37,4 @@ class StoneQuarryInvestigating {
}
}

new StoneQuarryInvestigating()
export const stoneQuarryInvestigating = new StoneQuarryInvestigating()
2 changes: 1 addition & 1 deletion src/modules/quests/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
import './learning/index'
import './learning/learning'
Loading

0 comments on commit 45d3259

Please sign in to comment.