feat: fishing and cooking
This commit is contained in:
@@ -1,18 +1,24 @@
|
||||
/**
|
||||
* Контент M2: мир-полоса зон (лес | рудник | кузница), ноды, навыки,
|
||||
* предметы, инструменты тирами, рецепты. Баланс правится данными здесь.
|
||||
* Контент M3: полная полоса зон (лес | рудник | кузница | река | кухня),
|
||||
* ноды (деревья, жилы, рыбные места), навыки, предметы, инструменты,
|
||||
* рецепты и отдых у костра. Баланс правится данными здесь.
|
||||
*/
|
||||
|
||||
import type { ZoneDef } from './protocol';
|
||||
|
||||
export const WORLD = { w: 2100, h: 720, spawn: { x: 180, y: 520 } } as const;
|
||||
export const WORLD = { w: 3200, h: 720, spawn: { x: 180, y: 520 } } as const;
|
||||
|
||||
export const ZONES: ZoneDef[] = [
|
||||
{ id: 'forest', name: 'Лес', x: 0, y: 0, w: 1000, h: 720 },
|
||||
{ id: 'mine', name: 'Рудник', x: 1000, y: 0, w: 680, h: 720 },
|
||||
{ id: 'forge', name: 'Кузница', x: 1680, y: 0, w: 420, h: 720 },
|
||||
{ id: 'river', name: 'Река', x: 2100, y: 0, w: 680, h: 720 },
|
||||
{ id: 'kitchen', name: 'Кухня', x: 2780, y: 0, w: 420, h: 720 },
|
||||
];
|
||||
|
||||
/** Уровень воды в зоне реки: выше — вода, ниже — берег. */
|
||||
export const RIVER_WATER_Y = 360;
|
||||
|
||||
// ---- лес ----
|
||||
|
||||
export const TREE_SPOTS: { x: number; y: number }[] = [
|
||||
@@ -61,7 +67,50 @@ export const ORES: Record<
|
||||
/** Точка, куда встаёт аватар для ковки. */
|
||||
export const FORGE_SPOT = { x: 1830, y: 415 } as const;
|
||||
|
||||
export const PROPS = [{ kind: 'anvil', x: 1830, y: 370 }] as const;
|
||||
/** Точка отдыха у костра. */
|
||||
export const REST_SPOT = { x: 1960, y: 515 } as const;
|
||||
|
||||
/** Точка ковки на кухне (у печи). */
|
||||
export const KITCHEN_SPOT = { x: 2990, y: 425 } as const;
|
||||
|
||||
export const PROPS = [
|
||||
{ kind: 'anvil', x: 1830, y: 370 },
|
||||
{ kind: 'campfire', x: 1960, y: 480 },
|
||||
{ kind: 'stove', x: 2990, y: 380 },
|
||||
] as const;
|
||||
|
||||
// ---- река ----
|
||||
|
||||
export interface FishSpot {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export const FISH_SPOTS: FishSpot[] = [
|
||||
{ x: 2250, y: 330 },
|
||||
{ x: 2450, y: 240 },
|
||||
{ x: 2650, y: 330 },
|
||||
];
|
||||
|
||||
export const FISH = {
|
||||
item: 'raw_fish',
|
||||
level: 1,
|
||||
xp: 15,
|
||||
cycleMs: 14_000,
|
||||
respawnMs: 60_000,
|
||||
maxHp: 4,
|
||||
} as const;
|
||||
|
||||
// ---- отдых у костра ----
|
||||
|
||||
export const REST = {
|
||||
/** Длительность одного отдыха (план: «+20% добычи за 1 минуту отдыха»). */
|
||||
cycleMs: 60_000,
|
||||
buffMs: 300_000,
|
||||
maxStacks: 2,
|
||||
/** Прибавка к добыче за стак (дробные циклы копятся аккумулятором). */
|
||||
bonusPerStack: 0.2,
|
||||
} as const;
|
||||
|
||||
// ---- навыки ----
|
||||
|
||||
@@ -69,6 +118,8 @@ export const SKILLS = [
|
||||
{ id: 'woodcutting', title: 'Рубка леса' },
|
||||
{ id: 'mining', title: 'Добыча руды' },
|
||||
{ id: 'smithing', title: 'Кузнечное дело' },
|
||||
{ id: 'fishing', title: 'Рыбалка' },
|
||||
{ id: 'cooking', title: 'Кулинария' },
|
||||
] as const;
|
||||
|
||||
export type SkillId = (typeof SKILLS)[number]['id'];
|
||||
@@ -87,6 +138,8 @@ export const ITEMS: Record<string, { title: string }> = {
|
||||
iron_ore: { title: 'железная руда' },
|
||||
copper_bar: { title: 'медный слиток' },
|
||||
iron_bar: { title: 'железный слиток' },
|
||||
raw_fish: { title: 'сырая рыба' },
|
||||
cooked_fish: { title: 'жареная рыба' },
|
||||
axe_rusty: { title: 'ржавый топор' },
|
||||
axe_iron: { title: 'железный топор' },
|
||||
pick_rusty: { title: 'ржавая кирка' },
|
||||
@@ -114,7 +167,7 @@ export const TOOLS: ToolDef[] = [
|
||||
{ item: 'pick_iron', kind: 'pick', multiplier: 2 },
|
||||
];
|
||||
|
||||
// ---- рецепты (ковка) ----
|
||||
// ---- рецепты ----
|
||||
|
||||
export interface RecipeDef {
|
||||
id: string;
|
||||
@@ -122,9 +175,12 @@ export interface RecipeDef {
|
||||
output: { item: string; qty: number };
|
||||
inputs: { item: string; qty: number }[];
|
||||
cycleMs: number;
|
||||
/** Требование к навыку кузнечного дела. */
|
||||
/** Требование к навыку recipe.skill. */
|
||||
level: number;
|
||||
xp: number;
|
||||
/** Какой навык качает рецепт и где готовится. */
|
||||
skill: 'smithing' | 'cooking';
|
||||
place: 'forge' | 'kitchen';
|
||||
aliases: string[];
|
||||
}
|
||||
|
||||
@@ -137,6 +193,8 @@ export const RECIPES: RecipeDef[] = [
|
||||
cycleMs: 8_000,
|
||||
level: 1,
|
||||
xp: 15,
|
||||
skill: 'smithing',
|
||||
place: 'forge',
|
||||
aliases: ['медный слиток', 'медь'],
|
||||
},
|
||||
{
|
||||
@@ -147,6 +205,8 @@ export const RECIPES: RecipeDef[] = [
|
||||
cycleMs: 10_000,
|
||||
level: 2,
|
||||
xp: 25,
|
||||
skill: 'smithing',
|
||||
place: 'forge',
|
||||
aliases: ['железный слиток', 'железо'],
|
||||
},
|
||||
{
|
||||
@@ -160,6 +220,8 @@ export const RECIPES: RecipeDef[] = [
|
||||
cycleMs: 15_000,
|
||||
level: 3,
|
||||
xp: 50,
|
||||
skill: 'smithing',
|
||||
place: 'forge',
|
||||
aliases: ['железный топор', 'топор'],
|
||||
},
|
||||
{
|
||||
@@ -173,13 +235,42 @@ export const RECIPES: RecipeDef[] = [
|
||||
cycleMs: 15_000,
|
||||
level: 3,
|
||||
xp: 50,
|
||||
skill: 'smithing',
|
||||
place: 'forge',
|
||||
aliases: ['железная кирка', 'кирка'],
|
||||
},
|
||||
{
|
||||
id: 'cooked_fish',
|
||||
title: 'жареная рыба',
|
||||
output: { item: 'cooked_fish', qty: 1 },
|
||||
inputs: [{ item: 'raw_fish', qty: 2 }],
|
||||
cycleMs: 10_000,
|
||||
level: 1,
|
||||
xp: 20,
|
||||
skill: 'cooking',
|
||||
place: 'kitchen',
|
||||
aliases: ['жареная рыба', 'рыба'],
|
||||
},
|
||||
];
|
||||
|
||||
/** Грубый стем для склонений: «жареную рыбу» → «жар», «рыб». */
|
||||
function stem(word: string): string {
|
||||
return word.slice(0, 3);
|
||||
}
|
||||
|
||||
export function findRecipe(text: string): RecipeDef | undefined {
|
||||
const q = text.trim().toLowerCase().replace(/\s+/g, ' ');
|
||||
return RECIPES.find((r) => r.id === q || r.title === q || r.aliases.includes(q));
|
||||
const exact = RECIPES.find((r) => r.id === q || r.title === q || r.aliases.includes(q));
|
||||
if (exact) return exact;
|
||||
// фолбэк для склонений: каждое слово запроса должно попасть в слова рецепта
|
||||
const qStems = q.split(' ').map(stem);
|
||||
if (qStems.length === 0) return undefined;
|
||||
return RECIPES.find((r) => {
|
||||
const stems = new Set(
|
||||
[...r.title.split(' '), ...r.aliases.flatMap((a) => a.split(' '))].map(stem),
|
||||
);
|
||||
return qStems.every((s) => stems.has(s));
|
||||
});
|
||||
}
|
||||
|
||||
// ---- прочее ----
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* дельты вместо полных снапшотов, игровые события, серверная камера.
|
||||
*/
|
||||
|
||||
export const PROTOCOL_VERSION = 3;
|
||||
export const PROTOCOL_VERSION = 4;
|
||||
|
||||
export interface ZoneDef {
|
||||
id: string;
|
||||
@@ -20,7 +20,7 @@ export interface ZoneDef {
|
||||
}
|
||||
|
||||
export interface PropDef {
|
||||
kind: 'anvil';
|
||||
kind: 'anvil' | 'campfire' | 'stove';
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
@@ -33,7 +33,7 @@ export interface WorldDef {
|
||||
props: PropDef[];
|
||||
}
|
||||
|
||||
export type AvatarAction = 'idle' | 'chop' | 'mine' | 'smith';
|
||||
export type AvatarAction = 'idle' | 'chop' | 'mine' | 'smith' | 'cook' | 'fish' | 'rest';
|
||||
|
||||
export interface SkillState {
|
||||
xp: number;
|
||||
@@ -64,12 +64,14 @@ export interface AvatarState {
|
||||
actionStart: number | null;
|
||||
actionDur: number | null;
|
||||
skills: Record<string, SkillState>;
|
||||
/** Стаки баффа отдыха (каждый +20% к добыче). */
|
||||
restStacks: number;
|
||||
}
|
||||
|
||||
export interface NodeState {
|
||||
id: string;
|
||||
kind: 'tree' | 'rock';
|
||||
/** Для руды — какая жила; для дерева отсутствует. */
|
||||
kind: 'tree' | 'rock' | 'fish';
|
||||
/** Для руды — какая жила, для рыбного места — 'river'. */
|
||||
variant?: string;
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -102,6 +104,7 @@ export type GameEvent =
|
||||
| { k: 'levelup'; userId: string; name: string; color: string; skill: string; level: number }
|
||||
| { k: 'fell'; userId: string; name: string; color: string; nodeId: string }
|
||||
| { k: 'depleted'; userId: string; name: string; color: string; nodeId: string }
|
||||
| { k: 'rest'; userId: string; name: string; color: string; stacks: number }
|
||||
| { k: 'blocked'; userId: string; name: string; color: string; reason: string };
|
||||
|
||||
export interface WelcomeMsg {
|
||||
|
||||
Reference in New Issue
Block a user